Django logo

Publishing a Django Website behind a proxy server

We use proxy servers all the time: we have a main server (eg http://edwards.sdsu.edu/) that serves applications (eg. http://edwards.sdsu.edu/GenomePeek) but the application itself runs on different hardware than the webserver.

Here, we show how to host a Django project on a proxy server using the apache web server and make it accessible.

First, you need your Django application up and running in your development location. Second, you need an apache server running. You should ensure that you have installed mod_wsgi (for that, your operating system distro probably has a library you can install [e.g. with apt or dnf]). On the host that is exposed to the internet, you need mod_proxy installed as well.

I am not going to show you how to do those here, there are plenty of tutorials on setting up servers. This is explicitly how to set the widgets on your host, your server, and within Django.

My setup:

  • The external server: https://edwards.sdsu.edu is exposed to the internet
  • The server behind the proxy: http://12.34.56.78 (not its real IP addresss!)
  • On that server (12.34.56.78) there is a user called rob with a Django web application called phages in /home/rob/phages
  • We are going to make the web application appear on https://edwards.sdsu.edu/phages (it doesn’t appear there at the moment)

On the external server

This is the machine exposed to the internet, and is just a proxy server. It is going to accept the requests to https://edwards.sdsu.edu/phages and return the pages from the Django web application.

In /etc/httpd/conf.d create a file called proxypass.conf that has these two lines:

ProxyPass /phages http://130.191.93.89/phages
ProxyPassReverse /phages http://130.191.93.89/phages

And now restart the httpd server

service httpd restart

or

systemctl restart httpd

On the application server

The main things that we need to do are:

  1. Set up the application server (optional)
  2. Set up mod_wsgi for the server
  3. Change permissions so that the apache user can access the application
  4. Configure the application

Set up the application server

This is a completely optional step. I am starting from a plain CentOS8 install and had to make a couple of tweaks:

# install apache and python virtual environment
dnf install httpd mod_wsgi virtualenv
# allow access through the firewall
firewall-cmd --zone=public --permanent --add-service=http --add-service=https
firewall-cmd --reload
firewall-cmd --list-all
# install elinks to test the server
yum --enablerepo=PowerTools install elinks
# start and enable the httpd server
systemctl start httpd.service
systemctl enable httpd.service

Set up mod_wsgi

There are two good tutorials that help with this set up, on the Django documentation page and on Digital Oceans. Most of the information here comes from those two sites, but I had to tweak some issues.

We are going to set up the Django wsgi call as a Daemon Process

In /etc/httpd/conf.d create a file called django.conf

WSGIDaemonProcess phages python-path=/home/rob/phages:/home/rob/phages/venv/lib/python3.6/site-packages
WSGIProcessGroup phages
WSGIScriptAlias /phages /home/rob/phages/phages/wsgi.py

Alias /static /home/rob/phages/static/
<Directory /home/rob/phages/static>
Require all granted
</Directory>

<Directory /home/rob/phages/phages>
   <Files wsgi.py>
      Require all granted
   </Files>
</Directory>

This sets up several items:

  • WSGIDaemonProcess phages python-path=/home/rob/phages:/home/rob/phages/venv/lib/python3.6/site-packages is the WSGI Daemon. This provides it access to the root level of the phages application, and also the specific location of the site-packages in the virtual environment for this application
  • WSGIProcessGroup phages just sets up a specific process group for this application. It means that if I have two web applications on this server, they won’t stomp over each other (see the Django documentation for more information)
  • WSGIScriptAlias /phages /home/rob/phages/phages/wsgi.py tells the server that if a request is made to /phages to redirect it to the wsgi.py script in my application (this is what Django uses to process your requests).
  • Alias /static /home/rob/phages/static/ sets up any static pages in your application. Don’t forget to run collectstatic (see below)
  • <Directory /home/rob/phages/static> provides access to all the files to everyone
  • <Directory /home/rob/phages/phages> only provides access to wsgi.py to everyone

Set up permissions

We need to set the permissions and ownership of some of our files so that apache can access them. Note, I strongly recommend you ls -l each of these before and after chmodding them to check the permissions!

Add the apache user to your user’s group

usermod -a -G rob apache

Provide access to the users home directory

chmod 710 /home/rob

Provide access to the sqlite database in the web application

chmod 664 ~rob/phages/db.sqlite3

Allow the user to write to the database

chown :apache /home/rob/phages/db.sqlite3

Allow apache to access the project

chown :apache /home/rob/phages

Enable apache to access the home directories in SELinux. If you are using SELinux (you should), you will need to set the appropriate boolean. First, find out which one:

grep httpd /var/log/audit/audit.log | audit2allow

and then check and set the status of that boolean:

getsebool httpd_read_user_content
setsebool httpd_read_user_content on
getsebool httpd_read_user_content

You may need to revisit this step later if you get an error when running the apache server.

Set additional permissions

Another approach to checking SELinux booleans is to use

audit2why -a | less

This will show you more detailed error messages. One in particular is worth paying attention to if it refers to a library (for example, a file ending cpython-36m-x86_64-linux-gnu.so)

Missing type enforcement (TE) allow rule.

This probably means that you need to set the appropriate settings to ensure that apache is allowed to execute this code block.

chcon -R -h -t httpd_sys_script_exec_t ./venv/lib/python3.6/site-packages/FastaValidator.cpython-36m-x86_64-linux-gnu.so

Set up the Django application

Clone your repository using git

git clone 

Set up a new virtual environment for your application and then activate it and use pip to install the requirements

cd phages
virtualenv venv
source venv/bin/activate
pip3 install -r requirements.txt

Note at this point you might need to find your site-packages to confirm the entries you added to django.confabove:

ls -d $PWD/$(find . -name site-packages -printf '%P\n')

Now you should be able to use the normal manage.py to run a server on the local machine, changing the IP address as appropriate

python3 manage.py runserver 12.34.56.78

and then test that server. Note, if you are using a terminal I recommend using elinks to access the local server. It will show you that you have access, although some of the javascript and other things may not work properly.

You may need to edit ALLOWED_HOSTS in settings.py to make sure you can access the machine. You will need to add your outside server (the one that has the proxy on it) to ALLOWED_HOSTS too!

We will collect all the static files so that they are served properly:

python3 manage.py collectstatic

Now you should be able to access your site using mod_wsgi and apache.

Start at your base URL:

links http://12.34.56.78

If you can not access the website:

  1. Check you /var/log/httpd/error_log while loading the page. If you see a client denied by server configuration then:
    1. Check the file that is actually trying to be accessed by that error
    2. Use audit2allow to make sure SELinux is not blocking access (see above)
  2. restart the httpd server to make sure all changes have been applied systemctl restart httpd.service

Cleaning up Django

Reset your SECRET_KEY and turn of DEBUG

The SECRET_KEY is used in a variety of places to control access, but it is used on a per-instance basis. You should be able to reset your secret key between runs of the server.

First, generate a new secret key by starting a python console in your virtual env that has Django installed:

from django.core.management.utils import get_random_secret_key
print(get_random_secret_key()) 

This will give you a new secret key that you can use in your application. We will put this in local_settings.py which, by default, is ignored in version control. Create a file called in your applications (in the same directory as settings.py) called local_settings.py that has these two lines:

DEBUG = False
SECRET_KEY = '(v3u!gc)nf-qbwka=2%p!rsmvyjs9w8mwzv!sd1fu)w2#0ch%_'

Note: Change the SECRET_KEY to be one that is not published on a website!

Next, edit settings.py and remove the two lines that define DEBUG and SECRET_KEY and instead, add this line

from .local_settings import SECRET_KEY, DEBUG

Now you can save settings.py in version control with no issues. You just need to remember to make a local_settings.py file on your development machine!

Note: You may also want to move ALLOWED_HOSTS from settings.py to local_settings.py too!

Check for deployment

Start by running the django built in tests for deployment:

python manage.py check --deploy

Writing to the server

The directions above are great for a standalone server that is going to only read from the SQL database, however there are some additional considerations that you should think about if you intend to write to the server.

First, SELinux (for good reasons) makes it very hard for apache to write to a user’s home directory. There are workarounds online, but the more appropriate solution is to move your application out of user space and into its own space, for example under /var/www/.

Once you have moved the directory, you will need to set the SELinux settings:

chcon -Rv --type=httpd_sys_rw_content_t /var/www/phages/

and ensure the permissions are set as above, and then you should be able to write to db.sqlite3