How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 18.04
Introduction
In this tutorial, you will install and configure some components on Ubuntu 18.04 to support and serve Django applications, set up a PostgreSQL database instead of using the default SQLite database, and then configure the Gunicorn application server to interface with your applications. Lastly, you’ll set up Nginx to reverse proxy to Gunicorn, giving you access to its security and performance features to serve your applications.
Prerequisites
You need following things to start work. 1. Dedicated ubuntu 18.04 server with root access. Or user with admin access. 2. Django application to be deployed on server.
Step 1 — Installing Packages from the Ubuntu Repositories
Start by downloading and installing all of the items you need from the Ubuntu repositories. Use the Python package manager pip
to install additional components later on.
First, update the local apt package index and then download and install the packages:
$ sudo apt update
Next, install your packages. This depends on which version of Python your project will use.
If you’re using Django with Python 3, run the following:
$ sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl
Django 1.11 is the last release of Django that will support Python 2. If you are starting new projects, it is strongly recommended that you choose Python 3. If you still need to use Python 2, run the following:
$ sudo apt install python-pip python-dev libpq-dev postgresql postgresql-contrib nginx curl
This will install pip
, the Python development files needed to build Gunicorn later, the Postgres database system and the libraries needed to interact with it, and the Nginx web server.
Step 2 — Creating the PostgreSQL Database and User(If DB instance is external then skip this step)
In this step, you’ll create a database and database user for your Django application with PostgreSQL, also known as “Postgres.”
By default, Postgres uses an authentication scheme called peer authentication for local connections. This means that if the user’s operating system username matches a valid Postgres username, that user can login with no further authentication.
During the Postgres installation, an operating system user named postgres was created to correspond to the postgres PostgreSQL administrative user. You need to use this user to perform administrative tasks. Log in to an interactive Postgres session by using sudo and pass in the username with the -u option:
$ sudo -u postgres psql
You’ll receive a PostgreSQL prompt where you can set up your requirements. First, create a database for your project:
postgres=# CREATE DATABASE myproject;
Next, create a database user for your project and select a secure password:
postgres=# CREATE USER myprojectuser WITH PASSWORD 'password';
Output
CREATE ROLE
After, modify a few of the connection parameters for the user you created. This will speed up database operations so that the correct values do not have to be queried and set each time a connection is established.
Now give your new user access to administer your new database:
postgres=# GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;
Output GRANT
When you are finished, exit out of the PostgreSQL prompt by running the following:
postgres=# \q
Postgres is now successfully set up so that Django can connect to and manage its database information.
Step 5 — Completing Initial Project Setup
- Upload your project on server's home directory using git, scp or ftp.
- Create virtual enviroment and install dependencies from
requirements.txt
file. - Configure database credentials in your django setting files.
- Add server IP in allowed hosts. (If domain avaiable add that in allowed hosts as well)
- On server need to make sure port is allowed. Use below command to allow port.
$ sudo ufw allow 8000
Note: On aws ec2 you have to create rule in security to allow port as well.
5. Run application using runserver
command on a port and check if application.
6. Check on browser if site is accessible from browser. Use IP/domain with port on http. For example If your server IP is XXX.XXX.XXX.XXX and app is running on port 8000. Then On browser http://XXX.XXX.XXX.XXX:8000.
Intial setup is complete for our application. We are to server it through nginx.
Note: Make sure to do manage.py collectstatic
for your project. It will gather static files in one place which will be serve rby server later. you can do this on local or server anywhere.
Step 6 — Testing Gunicorn’s Ability to Serve the Project
Before leaving your virtual environment, test Gunicorn to make sure that it can serve the application. You can do this by first moving into your project directory:
$ cd ~/myprojectdir
Then run gunicorn to load the project’s WSGI module:
$ gunicorn --bind 0.0.0.0:8000 myproject.wsgi
This will start Gunicorn on the same interface that the Django development server was running on. You can go back and test the application again.
When you are finished testing, press CTRL + C in the terminal window to stop Gunicorn.
Now you’re finished configuring your Django application and can deactivate your virtual environment:
$ deactivate
The virtual environment indicator in your prompt will be removed.
Step 7 — Creating systemd Socket and Service Files for Gunicorn
Now that you’ve tested that Gunicorn can interact with your Django application, you should implement a more robust way of starting and stopping the application server. To accomplish this, you’ll create systemd service and socket files.
The Gunicorn socket will be created at boot and will listen for connections. When a connection occurs, systemd will automatically start the Gunicorn process to handle the connection.
Start by creating and opening a systemd socket file for Gunicorn with sudo privileges in your preferred text editor:
$ sudo nano /etc/systemd/system/gunicorn.socket
Inside, create a [Unit] section to describe the socket, a [Socket] section to define the socket location, and an [Install] section to make sure the socket is created at the right time:
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
[Install]
WantedBy=sockets.target
Save and close the file when you are finished.
Next, create and open a systemd service file for Gunicorn with sudo privileges in your preferred text editor. The service filename should match the socket filename with the exception of the extension:
sudo nano /etc/systemd/system/gunicorn.service
Start with the [Unit] section, which is used to specify metadata and dependencies. Add a description of your service here and tell the init system to only start this service after the networking target has been reached. Because your service relies on the socket from the socket file, you need to include a Requires directive to indicate that relationship:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
Next, add a [Service] section and specify the user and group that you want the process to run under. Provide your regular user account ownership of the process since it owns all of the relevant files. Then give group ownership to the www-data group so that Nginx can communicate with Gunicorn.
Afterward, map out the working directory and specify the command to run to start the service. In this case, specify the full path to the Gunicorn executable, which is installed within your virtual environment. Then bind the process to the Unix socket you created within the /run directory so that the process can communicate with Nginx. Log all data to standard output so that the journald process can collect the Gunicorn logs. You can also specify any optional Gunicorn tweaks here. For our example, we specified 3 worker processes:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myprojectdir
ExecStart=/home/sammy/myprojectdir/myprojectenv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application
Finally, add an [Install] section. This will tell systemd what to link this service to if you enable it to start at boot. You want this service to start when the regular multi-user system is up and running:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myprojectdir
ExecStart=/home/sammy/myprojectdir/myprojectenv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application
[Install]
WantedBy=multi-user.target
With that, your systemd service file is complete. Save and close the file now.
Next, start the Gunicorn socket. This will create the socket file at /run/gunicorn.sock now and at boot:
sudo systemctl start gunicorn.socket
Then enable it so that when a connection is made to that socket, systemd will automatically start the gunicorn.service to handle it:
sudo systemctl enable gunicorn.socket
You can confirm that the operation was successful by checking for the socket file.
Step 8 — Checking for the Gunicorn Socket File
Check the status of the process to find out whether it was able to start:
sudo systemctl status gunicorn.socket
Output
● gunicorn.socket - gunicorn socket
Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor prese>
Active: active (listening) since Thu 2021-12-02 19:58:48 UTC; 14s ago
Triggers: ● gunicorn.service
Listen: /run/gunicorn.sock (Stream)
Tasks: 0 (limit: 1136)
Memory: 0B
CGroup: /system.slice/gunicorn.socket
Dec 02 19:58:48 gunicorn systemd[1]: Listening on gunicorn socket.
Next, check for the existence of the gunicorn.sock file within the /run directory:
file /run/gunicorn.sock
Output
/run/gunicorn.sock: socket
If the systemctl status command indicates that an error occurred or if you do not find the gunicorn.sock file in the directory, it’s an indication that the Gunicorn socket was not created correctly. Check the Gunicorn socket’s logs by running the following:
sudo journalctl -u gunicorn.socket
Check your /etc/systemd/system/gunicorn.socket file to fix any problems before continuing.
Step 9 — Testing Socket Activation
If you only started the gunicorn.socket unit, the gunicorn.service will not be active yet since the socket has not received any connections. Check the status:
sudo systemctl status gunicorn
Output
● gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
Active: inactive (dead)
To test the socket activation mechanism, send a connection to the socket through curl:
curl --unix-socket /run/gunicorn.sock localhost
You should receive the HTML output from your application in the terminal. This confirms that Gunicorn was started and able to serve your Django application. You can verify that the Gunicorn service is running by checking the status again:
sudo systemctl status gunicorn
Output
● gunicorn.service - gunicorn daemon
Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset
Active: active (running) since Tue 2021-11-23 22:55:12 UTC; 11s ago
Main PID: 11002 (gunicorn)
Tasks: 4 (limit: 1151)
CGroup: /system.slice/gunicorn.service
├─11002 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/
├─11018 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/
├─11020 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/
└─11022 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/
If the output from curl or the output of systemctl status indicates that a problem occurred, check the logs for additional details:
sudo journalctl -u gunicorn
Check your /etc/systemd/system/gunicorn.service file for problems. If you make changes to the /etc/systemd/system/gunicorn.service file, reload the daemon to reread the service definition:
sudo systemctl daemon-reload
Then restart the Gunicorn process:
sudo systemctl restart gunicorn
If any issues such as these occur, troubleshoot them before continuing.
Step 10 — Configure Nginx to Proxy Pass to Gunicorn
Now that Gunicorn is set up, next you’ll configure Nginx to pass traffic to the process.
Start by creating and opening a new server block in Nginx’s sites-available directory:
sudo nano /etc/nginx/sites-available/myproject
Inside, open up a new server block. Start by specifying that this block should listen on the normal port 80 and that it should respond to your server’s domain name or IP address:
server {
listen 80;
server_name server_domain_or_IP;
}
Next, instruct Nginx to ignore any problems with finding a favicon. Also, tell it where to find the static assets that you collected in your ~/myprojectdir/static directory. All of these files have a standard URI prefix of /static, so you can create a location block to match those requests:
server {
listen 80;
server_name server_domain_or_IP;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/sammy/myprojectdir;
}
}
Finally, create a location / {} block to match all other requests. Inside of this location, include the standard proxy_params file from the Nginx installation and then pass the traffic directly to the Gunicorn socket:
server {
listen 80;
server_name server_domain_or_IP;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/sammy/myprojectdir;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Save and close the file when you are finished. Now, you can enable the file by linking it to the sites-enabled directory:
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled
Test your Nginx configuration for syntax errors:
sudo nginx -t
If no errors are reported, go ahead and restart Nginx:
sudo systemctl restart nginx
Since you no longer need access to the development server, adjust your firewall settings by removing the rule to open port 8000:
sudo ufw delete allow 8000
Then allow normal traffic on ports 80 and 443 — thereby allowing HTTP and HTTPS connections, respectively — with the following command:
sudo ufw allow 'Nginx Full'
You should now be able to go to your server’s domain or IP address to view your application.