Using Django with Nginx, PostgreSQL, and Gunicorn on Debian 11
Introduction
Django is a free and open-source Python web framework. This article provides a step-by-step guide to setting up a Django project on a Debian 11 server with Nginx, Gunicorn, and a free Let's Encrypt TLS certificate.
Prerequisites
- Deploy a new Debain 11 server at Vultr
- Create a sudo user. The example user is
django_user
- Update the Server
- Point a domain name to the server. A domain name is required for Let's Encrypt SSL/TLS certificates. This guide uses the apex domain example.com and the fully-qualified domain name mydjango.example.com, and assumes the same IP address is assigned to both names.
1. Install Dependencies
Log in to the server with a non-root sudo user via SSH.
$ ssh django_user@mydjango.example.com
Install
UFW
,Vim
, andNginx
with the apt package manager.$ sudo apt -y install ufw vim nginx
Allow all outbound traffic.
$ sudo ufw default allow outgoing
Block all inbound traffic except SSH (port 22), HTTP (port 80), and HTTPS (port 443).
$ sudo ufw default deny incoming $ sudo ufw allow ssh $ sudo ufw allow http $ sudo ufw allow https
Enable and reload
UFW
.$ sudo ufw enable $ sudo ufw reload
2. Install PostgresSQL
Install PostgreSQL with the apt package manager, a postgres user is created during the installation.
$ sudo apt install -y postgresql postgresql-contrib
Switch to the postgres user.
$ sudo su - postgres
Log in to PostgresSQL.
$ psql
Create a PostgresSQL database role for the Django project.
postgres=# CREATE ROLE dbuser WITH LOGIN;
Set password for
dbuser
role.postgres=# \password dbuser
Set
dbuser
encoding toUTF-8
.postgres=# ALTER ROLE dbuser SET client_encoding TO 'utf8';
Set
dbuser
default transaction isolation to read commit.postgres=# ALTER ROLE dbuser SET default_transaction_isolation TO 'read committed';
Set
dbuser
timezone toUTC
.postgres=# ALTER ROLE dbuser SET timezone TO 'UTC';
Create the Django project database.
postgres=# CREATE DATABASE appdb;
Grant the
dbuser
all privileges toappdb
.postgres=# GRANT ALL PRIVILEGES ON DATABASE appdb TO dbuser;
Exit the postgres client.
postgres=# \q
Exit to sudo
exit
Restart the PostgresSQL server.
$ sudo systemctl restart postgresql
3. Setup the Python Environment
A Python virtual environment should be used when deploying Python web apps.
Install the Python
venv
.$ sudo apt -y install python3-venv
Create app dir, where the virtual environment will be, and the app files. example
app_dir
. Change into the home directory.$ cd ~
Create the directory.
$ mkdir app_dir
Change to
app dir
and create the virtual environment.$ cd app_dir $ python3 -m venv venv
Activate the virtual environment.
$ source venv/bin/activate
Install Python PostgresSQL library
psycopg2-binary
instead of thepsycopg2
, aspsycopg2
will need to be built during installation.$ pip install psycopg2-binary
Install Django and Gunicorn.
$ pip install django gunicorn
4. Upload or Create the Django Project
The project source code needs to be uploaded to the app directory
app_dir
with git or scp, for this article, create a sample project calledexample_app
.$ django-admin startproject example_app .
Check the folder structure of the
app_dir
.$ ls example_app manage.py venv
Create a folder to hold the static files of the Django project inside the app folder.
$ mkdir static
Open the
settings.py
inexample_app
withVim
to configure the project.$ vim example_app/settings.py
Configure the allow host to the domain.
ALLOWED_HOSTS = ['mydjango.example.com']
Configure the
DATABASES
variable with the details created fromsection 2
.DATABASES = { "default" : { "ENGINE" : "django.db.backends.postgresql", "NAME" : "appdb", "USER" : "dbuser", "PASSWORD" : "dbpassword", "HOST" : "127.0.0.1", "PORT" : "5432", } }
Set the static folder path. Look for
STATIC_URL
and underneath it addSTATIC_ROOT
.STATIC_ROOT = "/home/django_user/app_dir/static"
Save and close the
settings.py
file.Create the project migrations.
$ python manage.py makemigrations
Run the migrations.
$ python manage.py migrate
Copy all the static files of the project to the static folder. Type
yes
when prompted.$ python manage.py collectstatic
Create an admin user for the project.
$ python manage.py createsuperuser
Allow port
8000
onUFW
.$ sudo ufw allow 8000
Test the project to make sure it runs without errors.
$ python manage.py runserver 0.0.0.0:8000 Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). November 16, 2022 - 21:19:26 Django version 4.1.3, using settings 'example_app.settings' Starting development server at http://0.0.0.0:8000/ Quit the server with CONTROL-C.
5. Configure Gunicorn
A Gunicorn service will be created to serve the project and start the project on server restarts.
Test Gunicorn to see if it can serve the project without errors.
$ gunicorn --bind 0.0.0.0:8000 example_app.wsgi
Press Ctrl+C to stop it.
Deactivate the virtual environment
$ deactivate
Edit the
settings.py
file withVim
.$ vim example_app/settings.py
Disable debug. Look for the variable
DEBUG
and set it toFalse
.DEBUG = False
Save and close the
settings.py
file.Deny incoming traffic to port 8000 with
UFW
.$ sudo ufw deny 8000
Create a
django_app.service
file inside/etc/systemd/system
directory.$ sudo vim /etc/systemd/system/django_app.service
Write the following into the file.
[Unit] Description=Gunicorn for the Django project After=network.target [Service] User=django_user Group=www-data WorkingDirectory=/home/django_user/app_dir Environment="PATH=/home/django_user/app_dir/venv/bin" ExecStart=/home/django_user/app_dir/venv/bin/gunicorn --workers 2 --bind unix:django_app.sock example_app.wsgi [Install] WantedBy=multi-user.target
* Under
[Service]
,USER=django_user
sets the user of the service, andGroup=www-data
sets the group of the service to the Nginx group.WorkingDirectory=/home/django_user/app_dir
sets the service working directory to the projects folder.Environment="PATH=/home/django_user/app_dir/venv/bin"
sets the virtualenv that the service should use.ExecStart=/home/django_user/app_dir/venv/bin/gunicorn --workers 2 --bind unix:django_app.sock example_app.wsgi
run gunicorn worker services for the project and binds them to a Unix socket for faster data exchange for the Nginx proxy server.
Save and close the file.
Enable the service to start at boot.
$ sudo systemctl enable django_app
Start the service.
$ sudo systemctl start django_app
6. Configure Nginx
An Nginx proxy server is needed to serve Django apps in production and to handle SSL certificates.
Configure a server block in Nginx by creating a file
django_app
in/etc/nginx/sites-available
.$ sudo vim /etc/nginx/sites-available/django_app
Write the following into the file.
server { # listen to port 80 (HTTP) listen 80; # listen to the domain name server_name example.com mydjango.example.com; location /static/ { alias /home/django_user/app_dir/static/; expires max; } location / { include proxy_params; # pass the request to the project service sock proxy_pass http://unix:/home/django_user/app_dir/django_app.sock; } }
Save and close the file.
Create a link for
django_app
to Nginx'ssites-enabled
.$ sudo ln -s /etc/nginx/sites-available/django_app /etc/nginx/sites-enabled
Check the Nginx configuration for any errors.
$ sudo nginx -t
Restart the Nginx server.
$ sudo systemctl reload nginx
7. (Optional) Add HTTPS with Let's Encrypt
If you configured a domain in the beginning, then you can add an SSL/TLS certificate with Let's Encrypt to provide HTTPS support.
Install
Certbot
with theNginx
plugin.$ sudo apt install certbot python3-certbot-nginx
Configure
Certbot
withNginx
.$ sudo certbot --nginx -d example.com -d mydjango.example.com
The first time running Certbot, an admin email for the certificates will be requested. Certbot will also configure Nginx and redirect all HTTP requests to HTTPS by configuring the server block.
Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator nginx, Installer nginx Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel): me@example.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must agree in order to register with the ACME server. Do you agree? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: y - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: y Account registered. Requesting a certificate for example.com and mydjango.example.com Performing the following challenges: http-01 challenge for example.com http-01 challenge for mydjango.example.com Waiting for verification... Cleaning up challenges Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/django_app Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/django_app Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/django_app Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/django_app - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations! You have successfully enabled https://example.com and https://mydjango.example.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Subscribe to the EFF mailing list (email: me@example.com). IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/example.com/privkey.pem Your certificate will expire on 2023-02-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
Restart
Nginx
.$ sudo systemctl reload nginx
- Visit the domain of the server
mydjango.example.com/admin
in your browser.
You now have a working Django site on Debian 11.