In this article you will see how to setup Nginx webserver for your Django application with detailed and actionable instructions.
Table of contents¶
Objectives¶
Given a fresh Ubuntu 20.04 LTS we will:
- setup nginx server
- Python environment
- create a Django project
- and run it with gunicorn
- create a certificate with Let's encrypt
- setup nginx server as a reverse proxy
Requirements¶
- A fresh Ubuntu 20.04 LTS server
- Terminal app & SSH client
- SSH key(optional, we will create one)
- A domain name(or a subdomain)
Setup Ubuntu with Nginx and Python 3.10¶
Log into your fresh Ubuntu box and let's update the packages and then install Python and Nginx.
ssh root@server-ip
Or if your cloud provider gave you a different username, e.g. AWS EC2 - it will be ubuntu user:
ssh ubuntu@server-ip
Update APT package sources:
sudo apt-get update
Upgrade all existing installed Ubuntu APT packages:
sudo apt-get upgrade -y
Install new packages:
sudo apt-get install -y nginx curl apt-transport-https ca-certificates software-properties-common haveged letsencrypt snapd
Install Python 3.10 on Ubuntu 20.04¶
Add the deadsnakes PPA to the APT package manager sources list as below:
sudo add-apt-repository ppa:deadsnakes/ppa -y
Install python3.10 and python3-pip:
sudo apt install -y python3.10 python3-pip python3.10-venv
Install Certbot on Ubuntu 20.04¶
sudo add-apt-repository -y universe
sudo snap install --classic certbot
Configure Let's Encrypt and Nginx¶
mkdir -p /var/www/letsencrypt/
chown www-data.www-data /var/www/letsencrypt/
if [ ! -f /etc/nginx/dhparams.pem ]
then openssl dhparam -dsaparam -out /etc/nginx/dhparams.pem 4096
fi
echo "
location /.well-known/acme-challenge {
root /var/www/letsencrypt/;
}
" > /etc/nginx/acme.conf
echo "
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
include /etc/nginx/acme.conf;
location / {
return 301 https://\$host\$request_uri;
}
}
" > /etc/nginx/sites-enabled/default
echo "log_format upstreamlog '[\$time_local] \$remote_addr - \$remote_user - \$server_name to: \$upstream_addr: \$request status - \$status - upstream_response_time \$upstream_response_time msec \$msec request_time \$request_time \$http_user_agent';" > /etc/nginx/conf.d/log.conf
service nginx restart
These commands are used for configuring an nginx server to use Let's Encrypt SSL/TLS certificates with the help of Certbot.
Here is a breakdown of what we have just done:
mkdir -p /var/www/letsencrypt/
: This command creates a directory/var/www/letsencrypt/
which will be used for hosting Let's Encrypt'swell-known
challenge.chown www-data.www-data /var/www/letsencrypt/
: This command changes the ownership of the directory/var/www/letsencrypt/
to thewww-data
user and group, which is used by the nginx web server.if [ ! -f /etc/nginx/dhparams.pem ]
: This is a conditional statement that checks if the file/etc/nginx/dhparams.pem
exists. If it does not exist, the following command will create it.openssl dhparam -dsaparam -out /etc/nginx/dhparams.pem 4096
: This command generates a Diffie-Hellman parameter file for secure key exchange between the server and clients. The resulting file is saved in/etc/nginx/dhparams.pem
.echo "location /.well-known/acme-challenge { ... }" > /etc/nginx/acme.conf
: This command creates a new file/etc/nginx/acme.conf
and adds alocation
block for serving Let's Encrypt'swell-known
challenge.echo "server { ... }" > /etc/nginx/sites-enabled/default
: This command creates a new file/etc/nginx/sites-enabled/default
and adds aserver
block for serving HTTP traffic and redirecting it to HTTPS.echo "log_format upstreamlog '[\$time_local] \$remote_addr - \$remote_user - \$server_name to: \$upstream_addr: \$request status - \$status - upstream_response_time \$upstream_response_time msec \$msec request_time \$request_time \$http_user_agent';" > /etc/nginx/conf.d/log.conf
: This command creates a new file/etc/nginx/conf.d/log.conf
and defines a custom log format for the nginx web server.service nginx restart
: This command restarts the nginx web server to apply the changes made to the configuration files.
Create an Ubuntu user app for running Django Project¶
Now since we don't want our app to work from the administrative user, let's create a separate user.
useradd -m -s /bin/bash app
mkdir /home/app/.ssh
cp ~/.ssh/authorized_keys /home/app/.ssh/authorized_keys
chown -R app:app /home/app/.ssh/
chmod 700 /home/app/.ssh
chmod 600 /home/app/.ssh/authorized_keys
These commands are used to create a new user app
, set up SSH access for this user, and restrict access to the user's home directory.
Here is what these commands mean:
1. sudo useradd -m -s /bin/bash app
: This command creates a new user app
with a home directory /home/app
and a login shell /bin/bash
.
2. sudo mkdir /home/app/.ssh
: This command creates a directory /home/app/.ssh
for storing SSH keys.
3. sudo cp ~/.ssh/authorized_keys /home/app/.ssh/authorized_keys
: This command copies the contents of the current user's authorized_keys
file to the authorized_keys
file in the app
user's home directory.
4. sudo chown -R app:app /home/app/.ssh/
: This command changes the ownership of the /home/app/.ssh
directory and its contents to the app
user and group.
5. sudo chmod 700 /home/app/.ssh
: This command sets the permission of the /home/app/.ssh
directory to 700
, which means only the owner of the directory (i.e., the app
user) can access it.
6. sudo chmod 600 /home/app/.ssh/authorized_keys
: This command sets the permission of the authorized_keys
file to 600
, which means only the owner of the file (i.e., the app
user) can read and write to it.
We will run our Django app from this app
user.
Create a simple Django project with gunicorn to work behind Nginx¶
Let's make a simple Django project that will live behind reverse proxy.
Open another terminal and ssh into the server as the app
user
ssh app@server-ip
Let's create a Django project and run it with gunicorn
.
- Create a virtual environment:
python3 -m venv myenv
This will create a new virtual environment in the "myenv" directory.
- Activate the virtual environment:
source myenv/bin/activate
This will activate the virtual environment so that any subsequent Python commands will use the virtual environment's Python interpreter.
- Install Django:
pip install django
This will install the latest version of Django in the virtual environment.
- Create a new Django project:
django-admin startproject myproject
This will create a new Django project in a directory called "myproject".
- Install Gunicorn:
pip install gunicorn
This will install Gunicorn in the virtual environment.
- Change into the project directory:
cd myproject
- Run Gunicorn:
gunicorn myproject.wsgi --log-file -
This will start Gunicorn and serve your Django application on the default port (8000).
Here are all commands at once so you can copy them:
python3.10 -m venv myenv
source myenv/bin/activate
pip install django
django-admin startproject myproject
pip install gunicorn
cd myproject
gunicorn myproject.wsgi --log-file -
That's it! You now have a Django application running with Gunicorn in a Python3.10 virtual environment.
Issue SSL certificate with Certbot¶
Switch to the terminal with the root user.
Make sure that your domain or subdomain has an A-record that points to your server IP.
Let's say the domain is example.com
certbot certonly --cert-name example.com --non-interactive --expand --email your.email@gmail.com --webroot -w /var/www/letsencrypt -d example.com --agree-tos
It should say that certificate is created. If it wasn't created, there is a chance that you have just updated DNS records and it needs time to propogate. Check DNS settings and try again in a few minutes.
Create Nginx configuration for Django project¶
Now let's create an nginx configuration for our Django app that will use our new SSL certificate.
Create a file /etc/nginx/sites-enabled/ssl.example.com.conf
with this content:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_prefer_server_ciphers on;
ssl_stapling on;
access_log /var/log/nginx/ssl.example.com.access.log upstreamlog;
error_log /var/log/nginx/ssl.example.com.errors.log warn;
client_max_body_size 100m;
include /etc/nginx/acme.conf;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_read_timeout 600;
proxy_send_timeout 600;
proxy_set_header Host \$http_host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Forwarded-Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header X-Request-Id \$pid-\$msec-\$remote_addr-\$request_length;
}
}
This configuration block is for an HTTPS server using Nginx as the web server. It listens on port 443 and serves the example.com domain.
Here's a brief explanation of each directive:
-
listen 443 ssl;
: Tells Nginx to listen on port 443 for HTTPS connections. -
server_name example.com;
: Specifies the domain name that the server will handle. -
ssl_certificate
andssl_certificate_key
: Specifies the paths to the SSL certificate and private key files. -
ssl_protocols
: Specifies the SSL/TLS protocols that are allowed. In this case, it allows TLSv1, TLSv1.1, and TLSv1.2. -
ssl_ciphers
: Specifies the cipher suites that are allowed for SSL/TLS connections. It is a list of ciphers that are prioritized by the server. -
ssl_dhparam
: Specifies the path to the Diffie-Hellman parameter file. -
ssl_prefer_server_ciphers on;
: Instructs Nginx to use the server's preferred ciphers rather than the client's. -
ssl_stapling on;
: Enables SSL stapling, which helps improve the security and performance of SSL connections. -
access_log
anderror_log
: Specifies the paths to the access and error log files. -
client_max_body_size
: Limits the maximum allowed size of the client request body. -
include /etc/nginx/acme.conf;
: Includes additional configuration directives from the acme.conf file. -
location /
: Specifies the URI location that will be handled by this configuration block. -
proxy_pass
: Specifies the address of the backend server to which requests will be proxied. -
proxy_read_timeout
andproxy_send_timeout
: Specifies the maximum time allowed for reading and sending data to the backend server. -
proxy_set_header
: Sets the headers that will be sent to the backend server, such as theHost
andX-Real-IP
headers. It also sets additional headers to pass information about the connection, such asX-Forwarded-Proto
,X-Forwarded-Host
, andX-Forwarded-For
. -
proxy_set_header Upgrade $http_upgrade;
andproxy_set_header Connection upgrade;
: Sets the headers necessary for WebSocket connections. -
proxy_set_header X-Request-Id $pid-$msec-$remote_addr-$request_length;
: Sets a custom header with a unique request ID for debugging purposes.
Run service nginx reload
to apply changes to nginx configuration.
Conclusion¶
Now try opening your site at https://example.com.
Congratulations! You have successfully setup Ubuntu with Django and Nginx Reverse proxy and your site has SSL certificate!