Author: Michael Jalloh
Last Updated: Wed, Nov 23, 2022Django 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.
Deploy a new Debain 11 server at Vultr
Create a sudo user. The example user is django_user
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.
Log in to the server with a non-root sudo user via SSH.
$ ssh django_user@mydjango.example.com
Install UFW
, Vim
, and Nginx
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
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 to UTF-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 to UTC
.
postgres=# ALTER ROLE dbuser SET timezone TO 'UTC';
Create the Django project database.
postgres=# CREATE DATABASE appdb;
Grant the dbuser
all privileges to appdb
.
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
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 the psycopg2
, as psycopg2
will need to be built during installation.
$ pip install psycopg2-binary
Install Django and Gunicorn.
$ pip install django gunicorn
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 called example_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
in example_app
with Vim
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 from section 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 add STATIC_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
on UFW
.
$ 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.
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 with Vim
.
$ vim example_app/settings.py
Disable debug. Look for the variable DEBUG
and set it to False
.
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, and Group=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
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's sites-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
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 the Nginx
plugin.
$ sudo apt install certbot python3-certbot-nginx
Configure Certbot
with Nginx
.
$ 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
mydjango.example.com/admin
in your browser.You now have a working Django site on Debian 11.