Django/Deployments/

How to deploy a python Django application securely on a production server using Gunicorn, Nginx, and Letsencrypt?

Published on

How to deploy a python Django application securely on a production server using Gunicorn, Nginx, and Letsencrypt?
Pushing a Django Application from development to production is a demanding process, and has multiple steps to configure. Step by step process of the production deployment is explained in this blog.

Introduction:

Pushing a Django Application from development to production is a demanding process, with multiple steps to configure.

In this tutorial, we are going to host a Django Application on a remote ubuntu server. And run the Gunicorn as a web server locally and set up a reverse proxy using the Nginx web server.

Flow Diagram:


django-production-application-flowdiagram

Prerequisites:

  • Ubuntu server

  • Django App

  • Domain name

Steps:

Step 1: Connect to your server.

Please connect to your Ubuntu server to proceed with the following steps.

To connect with the server using SSH, refer to the article on Connect to your server with SSH.

To connect with the server using vs code, refer to the article on Connect to your server using vs code.

Step 2: Run the Django Application

Set up the Django project on your server and check if it is running.

Please refer to the article Getting started with Django on how to set up a Django project on a remote server.

By referring to the above article I've set up my Django project in the path /home/ubuntu/django_example_repo/ and my virtual environment in the path /home/ubuntu/.django_venv/ as shown in the following reference screenshot.


django-directory


So my django_project_directory would be /home/ubuntu/django_example_repo/ and my virtual_environment would be .django_venv.

When I refer to these folder structures please keep in mind to replace the commands with your respective Django project path.

When I refer to the Django core app, I’m referring to the app that you created when creating the project that has the files settings.py, wsgi.py, and asgi.py. My Django core app name is core.

Step 3: Collect static files

  • We need to collect the static files used in our Django project into a single directory because from now on we are using external servers to serve the files.

  • Now navigate to the Django core app directory.

1cd <django_project_directory>/<core_app> 2# in my case 3cd /home/ubuntu/django_example_repo/core/
  • Open the settings.py file and change the variable STATIC_ROOT as follow snippet, if the variable was not found then add the following line.

1STATIC_ROOT = BASE_DIR / 'static'
  • Now navigate to the Django root directory and run the following command.

1python3 manage.py collectstatic
  • It’ll copy the static files to a folder named static under the Django project directory.

An example screenshot of the above command is shown below for reference.


django-collectstatic-command-example


  • Navigate to the Django core app directory.

1cd <django_project_directory>/<core> 2 3For example. 4cd cd /home/ubuntu/django_example_repo/core/
  • Open the settings.py and append the following code to the file.

1# import the staticfiles_urlpatterns 2from django.contrib.staticfiles.urls import staticfiles_urlpatterns 3 4# below line have to be the last line of the file 5urlpatterns += staticfiles_urlpatterns()
  • An example django project for your reference can be found in the following git repository.

Step 4: Run the Django Application using Gunicorn

Gunicorn can be installed by using either APT package manager or python-pip.

  • We are going to install gunicorn using python-pip, to do so run the following command in the terminal.

1python3 -m pip install gunicorn

Now the gunicorn package will be installed as shown in the below screenshot.


django-application-using-gunicorn


  • Now we run the Django application using gunicorn in port 8000 which can be accessed from the public internet.

  • Navigate to the django_project_directory.

1cd <django_project_directory> 2# in my case 3cd /home/ubuntu/django_example_repo/
  • Run the following command to run gunicorn.

1gunicorn --bind <ip_address>:<port> <core_app>.wsgi:application 2# in my case 3gunicorn --bind 0.0.0.0:8000 core.wsgi:application

ip_address: tells gunicorn to run the application from which host. For the application to be accessed from the public internet give 0.0.0.0

port: from which port, are we going to run the application? We'll give 8000.

core_app: name of the Django core app. (My core app name is named core. Give the name of your django_core_app name)

The gunicorn will run the Django application as shown in the below screenshot.


running-django-using-gunicorn


  • Check the status by visiting the IP address with the port number. (<ip_address>:8000)



acess-django-http


accessing-django-rest-framework-http

Running Gunicorn as a service

  • We need to create a systemd for gunicorn for running it as a service so that the gunicorn can be run automatically and independently without manual start.

  • Navigate to the systemd directory and create a file named gunicorn.service.

1cd /etc/systemd/system/ 2sudo touch gunicorn.service
  • Now open the above-said file and add the following snippet.

1[Unit] 2Description=gunicorn daemon 3After=network.target 4 5[Service] 6User=ubuntu 7Group=ubuntu 8WorkingDirectory=/home/ubuntu/django_example_repo/ 9ExecStart=/home/ubuntu/.django_venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 core.wsgi:application 10ExecReload=/bin/kill -s HUP $MAINPID 11 12[Install] 13WantedBy=multi-user.target

WorkingDirectory Director path of Django project (in my case /home/ubuntu/django_example_repo)

ExecStart The command for starting gunicorn, gunicorn --workers 3 --bind 0.0.0.0:8000 core.wsgi:application, (full gunicorn path within the virtual environment, run the command, which gunicornand paste the output it returns). An example command execution is given below for reference.


django-gunicorn-path-directory


  • Run the following command so that the gunicorn service is loaded into the system.

1sudo systemctl daemon-reload
  • To start the gunicorn service run the below command.

1sudo systemctl start gunicorn.service
  • We need to enable the gunicorn service so that it can be started automatically when the server reboots.

1sudo systemctl enable gunicorn.service
  • Again check the status by visiting the IP address with the port number. (<ip_address>:8000)

  • To stop the gunicorn service run the below command.

  • 1sudo systemctl stop gunicorn.service
  • To restart the gunicorn service run the below command.

  • 1sudo systemctl restart gunicorn.service

An example gunicorn service file can be found in the following git repository.

Step 5: Setup reverse proxy using Nginx

Install Nginx by referring to the following article How to install Nginx on an Ubuntu server?

Setup nginx at port 80

  • Disable the default site by running the following command.

1sudo unlink /etc/nginx/sites-enabled/default
  • Now we need to create a new nginx configuration file for our (new endpoint/ domain/ site).

  • Navigate to the nginx (configuration/ sites-available) directory and create a file with the name of your convenience(usually domain_name is used, I’m using my domain name api.iternerays.com).

1cd /etc/nginx/sites-available/ 2sudo touch iternerays.com
  • Now open the file api.iternerays.com and add the following snippet.

1server { 2 listen 80; 3 server_name api.iternerays.com; 4 5location /static { 6 root /home/ubuntu/django_example_repo; 7} 8 9location / { 10 proxy_pass "http://127.0.0.1:8000"; 11 } 12}

location /static redirects the URL whose path starts with /static (like http://api.iternerays.com/static/) to the static file location.

or

to make nginx serve the static files.

location proxy_pass redirects the request coming to the server to the locally running gunicorn. We are redirecting the request to port 8000 at localhost in the above example.

  • An example site configuration file can be found in the following git repository.

  • Now link the file api.iternerays.com to the sites-enabled directory.

1sudo ln -s /etc/nginx/sites-available/api.iternerays.com /etc/nginx/sites-enabled/api.itinerarys.com
  • Restart both nginx and gunicorn to load the configuration changes by running the following commands.

1sudo systemctl restart nginx 2sudo systemctl restart gunicorn
  • Now check the status by visiting the domain name (In my case api.iternerays.com)


acess-django-using-domain-name


acess-django-using-http-domain-name-server

Step 6: Enable HTTPs on Nginx

To create SSL certificates we need a domain mapped to our server’s IP address.

Please refer to the article How to point an A record to IP address and point a domain to your server’s IP address.

Creating SSL certificate using Let's Encrypt

  • Install the certbot and nginx plugin using APT manager by running the below command.

1sudo apt install certbot python3-certbot-nginx -y
  • Then create SSL certificates using certbot by running the following command.

1sudo certbot --nginx -d <domain_name> 2 3# For example 4sudo certbot --nginx -d api.itinerarys.com

Enter the details asked by the certbot with your relevant data respectively.

While creating certificates certbot asks whether or not to redirect HTTP traffic to HTTPS, removing HTTP access, please choose option 2 for redirecting HTTP traffic to HTTPS.

  • After installing the SSL certificates, the nginx configuration file we created will now look like the below snippet.

1server { 2 server_name api.itinerarys.com; 3 4location /static { 5 root /home/ubuntu/django_example_repo; 6} 7 8location / { 9 proxy_pass "http://127.0.0.1:8000"; 10 } 11 12 listen 443 ssl; # managed by Certbot 13 ssl_certificate /etc/letsencrypt/live/api.itinerarys.com/fullchain.pem; # managed by Certbot 14 ssl_certificate_key /etc/letsencrypt/live/api.itinerarys.com/privkey.pem; # managed by Certbot 15 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot 16 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot 17 18} 19server { 20 if ($host = api.itinerarys.com) { 21 return 301 https://$host$request_uri; 22 } # managed by Certbot 23 24 25 listen 80; 26 server_name api.itinerarys.com; 27 return 404; # managed by Certbot 28 29 30}
  • A reference file can be found in the following git repository.

  • Now check the status by visiting the domain name. (In my case https://api.itinerarys.com/)



acess-django-https


acess-django-using-domain-name-https

Comments