Because of Redhat's killing off the promised long term support of CentOS (and breaking their original promise of not messing with CentOS), I have to find a LTS distro. I hope this document is still helpful, but I'm not going to update it anymore.
This guide starts after the first boot. I used the CentOS 8 minimal ISO and accepted all the defaults during installation. I assume you're already familiar with Django (this is not a Hello World Django tutorial).
Run the following commands as root, or prepend everything with sudo
if that's more your speed.
# Enable and start sshd systemctl enable sshd --now # Update the system dnf check-update dnf update -y # Restart reboot # Enable the EPEL repo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm # Enable the PowerTools repo dnf config-manager --set-enabled PowerTools # Install Python, Git, and Nginx (Postgres is more complicated). NOTE: openldap-devel is optional if you don't need it for your project. dnf install -y python38 python38-devel git nginx gcc openldap-devel # Download/enable the official Postgres repo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm # Disable the CentOS-provided Postgres dnf -qy module disable postgresql # Install PostgreSQL, contrib modules, development files, and PostGIS support (PostGIS support is optional) dnf install -y postgresql12-server postgresql12-contrib postgresql12-devel postgis25_12 # Initialize the database /usr/pgsql-12/bin/postgresql-12-setup initdb # Enable and run Postgres and Nginx systemctl enable postgresql-12 nginx --now
Now some Django-specific stuff
# Make a user. Your Django app will run as this user. adduser --system --home=/opt/django --create-home django # Install virtualenv dnf -y install python3-virtualenv # You can use su to switch to the django user now if you're inclined. # Create new virtual environment for your Django app virtualenv /opt/django/venv # Activate your virtual environment source /opt/django/venv/bin/activate
At this point, we take a break to set up a starter Django project to (re)familiarize ourselves with the Nginx/Gunicorn/Django stack. We can trash the starter project when we're done.
# Install Django and gunicorn pip install Django gunicorn # change to django directory cd /opt/django # Create a test app django-admin startproject djangotest # change to test project directory cd djangotest # Run migrations ./manage.py migrate # *TEMPORARILY* disable the firewall and SELinux so we can see if the Django server works systemctl stop firewalld && setenforce 0 # Run Django's built-in server (figure out what your external IP address is with ``ip addr``) ./manage.py runserver 0.0.0.0:8000
Now go to http://192.168.10.205:8000 (change the IP address obviously). You should get a DisallowedHost at /
error. This is normal at this point, but it proves that Django is working.
Edit the djangotest/settings.py
file to add the IP address to the ALLOWED_HOSTS
list.
Try running the Django test project through gunicorn:
gunicorn djangotest.wsgi -b 192.168.10.205
You should be able to get to the Django test project in your browser. This time it's gunicorn serving up the site instead of Django's built-in server.
Now we'll add Nginx into the mix. First, make sure Nginx is accessible by going to http://192.168.10.205 (default port of 80). You should see the default Nginx Redhat splash page.
Create a file called /etc/nginx/conf.d/test.conf
and add the following to it:
server { listen 80; server_name 192.168.10.205; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # This is the path to the UNIX socket that gunicorn listens to requests on proxy_pass http://unix:/opt/django/run/socket; } }
Now we restart Nginx and also run gunicorn so we can test our new configuration:
# Restart nginx to apply the config systemctl restart nginx # Create a folder for gunicorn's socket mkdir /opt/django/run # Give "group" and "others" read/execute permission on the /opt/django folder so that nginx can access the socket file chmod go+rx /opt/django /opt/django/run # Change to the test folder cd /opt/django/djangotest gunicorn --workers 3 --bind unix:/opt/django/run/socket djangotest.wsgi
Now visit the website and you should see the default Django page. After we confirm this all works, we can return to getting our real Django site working.
Here are some assumptions I'm making:
First, let's address Postgres' configuration:
# Switch to the postgres user. su - postgres # Create the database user for your Django site. When prompted about making the new user a superuser, I recommend answering yes. You can remove superuser permissions later. createuser --interactive --pwprompt # Create the database, assigning the owner to our djangodb user createdb -O djangouser djangodb
Edit the /var/lib/pgsql/12/data/pg_hba.conf
file. Underneath the local all all peer
line add host djangodb djangouser 127.0.0.1/32 md5
. Comment out the two lines that start with host all
. In the end your file should look something like this:
# TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only local all all peer host djangodb djangouser 127.0.0.1/32 md5 # IPv4 local connections: #host all all 127.0.0.1/32 ident # IPv6 local connections: #host all all ::1/128 ident # Allow replication connections from localhost, by a user with the # replication privilege. local replication all peer host replication all 127.0.0.1/32 ident host replication all ::1/128 ident
Reload Postgres to apply the changes, and try to connect with your Django database user account.
# Reload Postgres systemctl reload postgresql-12.service # Try to log in psql -U djangouser -h 127.0.0.1 djangodb
Assumptions:
myproject
. It will live in /opt/django/myproject
.main
.main.settings_for_prod_centos8
(so the full path will be /opt/django/myproject/main/settings_for_prod_centos8.py
).Time to get our real Django app running.
# Change to Django directory cd /opt/django # Clone your project's repo git clone https://github/yourname/myproject # We need pg_config so that we can build PostgreSQL support for Django (the pscyopg2 module) export PATH=/usr/pgsql-12/bin:$PATH # Install your requirements pip install -r myproject/requirements.txt # Change into your project directory cd myproject
Now create your main/settings_for_prod_centos8.py
and customize it with your database settings. The DATABASES
section will look like this:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'djangoudb', 'USER': 'djangouser', 'PASSWORD': 'whatever_you_set_earlier', 'HOST': '127.0.0.1', 'PORT': 5432, } }
Now we can try running Django's built-in server with your real project.
# Create the static folder mkdir /opt/django/static # Create the media folder mkdir /opt/django/media # Set DJANGO_SETTINGS_MODULE to your project' export DJANGO_SETTINGS_MODULE=main.settings_for_prod_centos8 # Run the migrations with the ''--plan'' parameter to see what Django will do to the database. ./manage.py migrate --plan # If you saw no errors, run the migrations for real ./manage.py migrate # Run collectstatic ./manage.py collectstatic # Test your app with the Django's runserver command ./manage.py runserver 0.0.0.0:8000
If you can access your app, congratulations! You did it! But you're not finished yet. Let's get Nginx working with our Django app.
# Set the owner of the directory to django chown django:django /opt/django/run # Make a copy of our Nginx test.conf file cp /etc/nginx/conf.d/test.conf /etc/nginx/conf.d/django.conf # Rename the test.conf file so Nginx doesn't load it (alternatively, delete it) mv /etc/nginx/conf.d/test.conf /etc/nginx/conf.d/test.unused
Update the /etc/nginx/conf.d/django.conf
file to include the /static/ files config and to change the path to the socket:
server { listen 80; server_name 192.168.10.205; location /static/ { root /opt/django; } location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://unix:/opt/django/run/socket; } }
Try running Nginx and Django with this configuration:
# Change to the folder where your app's manage.py lives before running the following commands. cd /opt/django/myproject # Restart Nginx systemctl restart nginx # Run Gunicorn gunicorn --workers 3 --bind unix:/opt/django/run/socket main.wsgi
The site should now load up and work! If it doesn't, try to do it better next time. Stop the gunicorn process.
To make Gunicorn/Django start automatically we have to create a Systemd service definition file /etc/systemd/system/django-gunicorn.service
.
[Unit] Description=Django/Gunicorn service After=network.target [Service] User=django Group=nginx WorkingDirectory=/opt/django/myproject ExecStart=/opt/django/venv/bin/gunicorn --workers 3 --bind unix:/opt/django/run/socket main.wsgi Environment=DJANGO_SETTINGS_MODULE=main.settings_for_prod_centos8 [Install] WantedBy=multi-user.target
Start the service:
systemctl enable django-gunicorn.service --now
And your site should be available! You did it! But you're NOT FINISHED. Time to secure everything!
If you were to simply turn SELinux and the firewall back on, you wouldn't be able to access your site because:
# Set the selinux context of the run (socket) directory chcon -R -t httpd_sys_rw_content_t /opt/django/run # Re-enable the firewall so we can configure it systemctl start firewalld # Allow port 80 and 443 through the firewall firewall-cmd --zone=public --permanent --add-service=http firewall-cmd --zone=public --permanent --add-service=https firewall-cmd --reload # Re-enable selinux setenforce 1 # If you were running some commands as root, make sure to reset the permissions on the project folder to the django user. chown -Rv django:django /opt/django/myproject # Remove "group" and "other" permissions from the project folder. We don't want others to see our settings file! chmod go-rx /opt/django/myproject # If you want to check permissions, run the following: sudo -u nginx cat /opt/django/myproject/main/settings_for_prod_centos8.py # the above command should fail with a "Permission denied" error. # Remove the PostgreSQL superuser permission from the django database user: su - postgres psql -d djangodb -c "ALTER USER djangouser WITH NOSUPERUSER;"
Your site should be accessible and somewhat secure. Somewhat? Yeah, you didn't secure SSH, you didn't install fail2ban, you didn't enable TLS, and you didn't set up any backups. You fool. You moron. Go learn how to do that somewhere else.
Reboot. If your site comes up and works without any further intervention, congratulations! You did it!