Setting up your Django website with Gunicorn, Nginx and GoDaddy

Shankar Y Bhavani
9 min readOct 10, 2022

--

Background

Recently, I was building my own website to showcase my portfolio. Target I kept was to do it in 7 days along with my work commitments in parallel. I had a very specific set of requirements (/ constraints) for my own reasons to achieve this.

In order to do this, I want to use Django f/w to develop website, Gunicorn as app server, Nginx as web server , Godaddy as DNS provider. I was surprised to see very less information on internet to achieve this. Will try to summarise as much as possible in this article on how I achieved it.

Terms to know

  • Application Server — An application server in general serves dynamic content to the hosted website. It holds business logic and often serves content to interactive parts of your website.
  • Web Server — A webserver in general serves static content to the hosted website. It serves images, files etc.; media content to your website.
  • HTTPS — It is secure Hyper Text Transfer Protocol which ingeneral is used for preventing Man-in-the-middle attack when HTTP is used. It enables browser to send and receive encrypted messages from webservers so that information can be exchanged securely.
  • Domain Name — These are key part of internet infrastructure which enables human understandable names to public IP’s which are hard to remember.
  • Elastic IP — These are static IP addresses reserved by AWS infrastructure, which can be used by general public to use.
  • A Record A stands for address, implies it is the address of the IP where you want your Domain to get redirected.

Components

  • OS — Ubuntu 20.04 LTS
  • Webserver — Nginx
  • App Server — Gunicorn
  • Hardware — t2.micro
  • Certbot — Certificate provider

Note: There are many ways to achieve what I am about to show, this is a specific case where I want to keep cost as low as possible and host a website.

Steps to follow

  1. Develop website using Django.
  2. Configure Gunicorn service.
  3. Configure Nginx.
  4. Procure static IP (elastic) address from respective provider (AWS).
  5. Test your application.
  6. Procure a domain from any provider.
  7. Map your IP.
  8. Fetch certificate and enable HTTPS.
  9. Run SSL test and improve score.

Develop website using Django

For the purpose of this article, I will not cover this step as there are many articles across internet that does job.

Configure Gunicorn Service

Before this step, verify if your website is able to interact with gunicorn server by using the following command

gunicorn --reload --bind 0.0.0.0:8000 <<YOUR APP WSGI INTERFACE>>

We cannot run the server everytime by running above command. In order to allow system to manage Gunicorn service we should create a Gunicorn Socket and Gunicorn Service filessystemd folder. This enable system to start Gunicorn service at appropriate times.

sudo vim /etc/systemd/system/gunicorn.socket

In that file paste the following code

[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target

Note: All these has to be in new lines, otherwise socket wont be able to understand what to do.

  • Unit section gives description of socket
  • Socket section tells socket location
  • Install section ensures creating Gunicorn server at right time.

Next we shall create Gunicorn service which will start our app server on system start.

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=<Your user name>
Group=<Your user group>
WorkingDirectory=<Folderpath to your website>
ExecStart=<Folderpath to your virtualenv>/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock <<YOUR APP WSGI INTERFACE>>
[Install]
WantedBy=multi-user.target

In the above snippet,

  • Unit — section mentions metadata of the service and also when to trigger.
  • Service — section mentions user, group and the process to run.
  • Install — section tells systemd when to start and to which process this service needs to be attached to.

Note: Make sure User and Group mentioned in the configuration have permissions to access the folder. In practice, Group is used as www-data so that Nginx have permissions to access.

Now that, we have set up the service we can enable it using following commands.

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

This starts the service. To check, the status of the service we can use the following command

sudo systemctl status gunicorn.socket

Output for the above command will be like this

You can test the setup is working or not, by running the following command

curl --unix-socket /run/gunicorn.sock localhost

This will hit the Gunicorn service running locally and return raw html index page of your website.

Troubleshooting

To see logs for your Gunicorn service you can use following command

sudo journalctl -u gunicorn

Note: Anytime you make changes to gunicorn.service file, you should reload the running daemon which can be done as follows

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

Configure Nginx server

Now, lets configure our webserver to process all incoming requests.

Install nginx, using following command

sudo apt-get install nginx

After installation, create configuration file for your site

sudo vim /etc/nginx/sites-available/<name>.conf

In that paste the following

server {

listen 80;
server_name <static-ip or domain>;
location = /favicon.ico { access_log off; log_not_found off; }

location /static/ {
root <path-to-static-files>;
}

location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}

}

Now, you can use root or alias in the above configuration under static section . The difference is alias need to be used when you are mentioning absolute path of static files. So, when you open files in alias you will see all static files. When root is mentioned you are giving the parent directory where static files are present.

In above configuration,

  • server — lines 3–5 represent your server should listen on port 80 and should respond to incoming requests on ip mentioned in service_name field.
  • location — First entry defines where static files are served.
  • location — Second entry defines where the incoming traffic should be passed on to. In our case, it is Gunicorn socket.

Note: Make sure www-data group has permissions to access static files folder. It is recommended to serve it from /var/www/html/ or any other path owned by www-data group.

After above step, create a softlink for the above file in sites-enabled folder.

sudo ln -s /etc/nginx/sites-available/<name>.conf /etc/nginx/sites-enabled

Now, this step is mandatory as Nginx configuration reads configurations from sites-enabled folder.

Part where nginx reads configurations

You can check if the syntax is correct or not by running tests on nginx .

sudo nginx -t

Above step will tell if there are any faults or issues with the setup. If the setup is successful, you will see the logs as below.

Reload nginx configuration.

sudo systemctl restart nginx

Troubleshooting

If you follow above steps properly you should ideally not face any issues, If there still some issues

  1. Check if your internet port is open or not. For AWS, you can check your SG inbound rules.
  2. Check logs using the following command
    sudo journalctl -u nginx | tail
  3. If the above step is not showing logs as per your convenience, you can check logs in this folder /var/logs/nginx/ . This will contain access logs and error logs in separate files which can be used as required.

Procure static IP (elastic) address from respective provider (AWS)

For the purpose of this article, I am not covering this step, as this is straight forward following AWS documentation.

Test your application

You can just open browser and type in your static ip or domain name, to verify this step. If there are any errors you are facing, just follow troubleshooting steps mentioned in Gunicorn and Nginx configurations.

Procure a domain from any provider

This is pretty much straight forward, I used GoDaddy to procure a domain of my preference.

Map your IP

There are many ways even to achieve this, but since I want to minimize cost and keep the process as simple as possible I chose the following route.

Just update A record field in the GoDaddy site with the static IP you bought. This will do the trick.

Fetch certificate and enable HTTPS

Enabling HTTPS is one the basic required security features to your website. To serve HTTPS content on your website, you need to get a certificate from paid certificate provider like Comondo, Digicert, Symantec etc.;, but for the purpose of this article, we use one of the famous open source tools named Certbot which is backed by Mozilla. Certbot uses LetsEncrypt certificates and provides a tool to automatically update them as and when required.

Before we start off with Certbot make sure your instance has port 443 enabled similar to enabling Internet port.

Certbot uses snapd packages Which you can install from here. This comes preinstalled in most of the Ubuntu flavours. I used Ubuntu 20.04 LTS version which had snapd already installed.

After that, run the following commands to ensure snapd is of latest of version.

sudo snap install core
sudo snap refresh core

Install Certbot in your machine.

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Now fetch the certificates to your domain by using the following command.

sudo certbot certonly --nginx

This will create certificates to use in the location /etc/letsencrypt/live/<domain>/fullchain.pem , /etc/letsencrypt/live/<domain>/privkey.pem .

Note: Certbot will automatically read nginx configuration from sites-enabled folder and generates certificates for the domains mentioned in the configuration.

Once, we have the certificates we can change nginx configuration as follows
Create a configuration file to store certificate information

sudo vim /etc/nginx/snippets/self-signed.conf

place the location of private and public keys in this file

ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;

Save the file and close it.

Add SSL params to a configuration file

sudo vim /etc/nginx/snippets/ssl-params.conf

place the following params in that file.

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

Save the file and close it.

Change your site configuration in nginx sites enabled folder as follows:

server {
listen 443 ssl;
listen [::]:443 ssl;
include snippets/self-signed.conf;
include snippets/ssl-params.conf;

server_name <YOUR DOMAIN>;
location = /favicon.ico { access_log off; log_not_found off; }location /static/ {
autoindex on;
root <YOUR STATIC FOLDER>;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
server {

listen 80;
server_name <YOUR DOMAIN>;
location = /favicon.ico { access_log off; log_not_found off; }
return 302 https://$server_name$request_uri;
}

The change w.r.t previous configuration is that we are redirecting all requests incoming onto port 80 to port 443 which is https.

Run the following command to see if https is enabled or not.

sudo ufw app list

Output for the above command should look as follows:

Restart your nginx and you should be able to redirect the site to https.

Run SSL test and improve score.

You can run SSL test on your domain using the following online tool SSL labs by Qualys company. Interface is simple to use and it looks as follows

Plug in your domain in the hostname and you should be able to see SSL score in about couple of minutes. With the above configuration you will get a score of B. To improve we can do the following steps

  • Update Diffie-Hellman parameters. This can be done as follows
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

This can be used to update in /etc/nginx/snippets/ssl-params.conf as follows

ssl_protocols TLSv1.2;
...
ssl_dhparam /etc/ssl/certs/dhparam.pem;
...
  • Enable HSTS header in your nginx configuration. This configuration makes sure that browser wont make calls to your domain or subdomain with http with in the max time period set as max-age. Change your /etc/nginx/sites-available/<name>.conf as follows
server { listen 443 ssl;
...
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

...
}
server {
listen 80;
....

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
...
}

Once you perform above steps you will get an A+ rating in the above tool.

Note: Cerbot comes with a tool that automatically renews certificates no need to do any additional changes.

With this we have now finally be able to deploy a django based framework website onto an ec2 instance and successfully enable https for the same.

--

--

Shankar Y Bhavani
Shankar Y Bhavani

No responses yet