Setting up your Django website with Gunicorn, Nginx and GoDaddy
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
- Develop website using Django.
- Configure Gunicorn service.
- Configure Nginx.
- Procure static IP (elastic) address from respective provider (AWS).
- Test your application.
- Procure a domain from any provider.
- Map your IP.
- Fetch certificate and enable HTTPS.
- 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 socketSocket
section tells socket locationInstall
section ensures creatingGunicorn
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 tellssystemd
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 inservice_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 isGunicorn
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.
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
- Check if your internet port is open or not. For AWS, you can check your SG inbound rules.
- Check logs using the following command
sudo journalctl -u nginx | tail
- 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.