Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

Deploying a Django application on a VPS transforms your project into a reliable production environment capable of handling real users. A proper deployment setup improves performance, security, scalability, and long term application stability for growing websites.
Many developers successfully build Django applications locally but face challenges while deploying them on a live Ubuntu server. Understanding each deployment step helps avoid configuration mistakes, downtime, unnecessary troubleshooting, and security issues later.
This guide explains how to deploy Django on an Ubuntu VPS using Gunicorn and Nginx, configure essential production settings, enable HTTPS, and verify everything works correctly before making your application publicly accessible.
Before deploying your Django application, it’s worth making sure your server and project are ready. Having the required software and permissions in place will help you avoid common deployment errors later in the process. The requirements below are suitable for most personal projects, business websites, APIs, and web applications running on a Linux VPS.
Your server should meet these minimum requirements:
| Requirement | Recommended |
| Operating System | Ubuntu 22.04 LTS or Ubuntu 24.04 LTS |
| RAM | Minimum 1 GB (2 GB or more recommended) |
| CPU | 1 vCPU minimum |
| Storage | At least 20 GB SSD or NVMe SSD |
| Access | Root user or a user with sudo privileges |
| Network | Public IPv4 address |
| SSH | Enabled for remote server access |
If you plan to host a larger Django application with higher traffic, consider using additional CPU cores, more memory, and NVMe storage for better performance.
This deployment relies on a few essential tools that work together to run your Django application efficiently.
Before you begin, ensure your Django project is in a working state on your local machine. If you’re using Git, push the latest version to your repository so it can be cloned easily on the VPS. If not, keep a copy of the project ready to upload via SFTP or another secure file transfer method.
It’s also a good idea to create a requirements.txt file that lists all your project’s Python dependencies. This makes it easy to install the same packages on the server with a single command and helps keep the deployment consistent.
While not required to complete the deployment, these items will make your production setup more complete:
Once these prerequisites are in place, you’re ready to connect to your Ubuntu VPS and begin the deployment process. In the next step, we’ll log in to the server using SSH, update the operating system, and prepare it for installing Django and its required components.
The first step is to connect to your Ubuntu VPS through SSH (Secure Shell). SSH provides an encrypted connection between your local computer and the remote server, allowing you to install software, manage files, and configure your Django application securely.
Most VPS providers send your server’s IP address, username, and password (or SSH key details) immediately after the VPS is created. Keep this information handy before proceeding.
Open your terminal (Linux/macOS) or use an SSH client such as PuTTY on Windows, then connect to your VPS using the following command:
ssh username@your_server_ip
For example:
ssh root@203.0.113.10
If you’re connecting to the server for the first time, you’ll see a message asking whether you trust the server’s fingerprint.
Are you sure you want to continue connecting (yes/no)?
Type:
yes
and press Enter.
Next, enter your password when prompted. If you’re using SSH keys, authentication will happen automatically without asking for the server password.
After a successful login, you’ll see a command prompt similar to this:
root@ubuntu:~#
or
username@ubuntu:~$
This indicates that you’re now connected to your Ubuntu VPS.
Before installing Python, Django, or any other software, update Ubuntu’s package index to ensure you’re downloading the latest available packages.
Run:
sudo apt update
This command refreshes your server’s package list from the official Ubuntu repositories.
After updating the package list, upgrade any outdated packages already installed on the server.
sudo apt upgrade -y
Depending on how recently the VPS was provisioned, this process may take several minutes.
Keeping your server updated helps:
If the upgrade installs a newer Linux kernel, Ubuntu may recommend restarting the VPS once the process finishes.
Before moving forward, verify that your server is running a supported Ubuntu release.
Use the following command:
lsb_release -a
or
cat /etc/os-release
You’ll see output similar to:
Distributor ID: Ubuntu
Description: Ubuntu 24.04 LTS
Release: 24.04
Codename: noble
Both Ubuntu 22.04 LTS and Ubuntu 24.04 LTS work well for Django deployments and receive long term security updates.
Some Ubuntu VPS images already include Python 3. You can verify its availability by running:
python3 --version
If Python is installed, you’ll see output similar to:
Python 3.12.x
If the command returns an error or an older version than expected, don’t worry, we’ll install the required Python packages in the next step.
With your Ubuntu VPS ready, the next step is to install Python and the system packages required to run a Django application in a production environment. Although many Ubuntu VPS images include Python by default, installing the latest available packages ensures you have all the tools needed for development, dependency management, and deployment.
If you haven’t updated the package repository recently, run the following command once more:
sudo apt update
This ensures Ubuntu installs the latest available package versions from its official repositories.
Now install Python 3, pip, the virtual environment package, and other commonly required development libraries.
sudo apt install python3 python3-pip python3-venv python3-dev build-essential libpq-dev -y
Here’s what each package does:
Note: If your project uses MySQL instead of PostgreSQL, you can install the appropriate MySQL development package later when needed.
Once the installation completes, confirm that Python is available on your server.
python3 --version
Example output:
Python 3.12.3
The exact version may vary depending on your Ubuntu release and installed updates.
Next, verify that pip was installed successfully.
pip3 --version
Example output:
pip 24.x.x from /usr/lib/python3/dist-packages/pip
This confirms that Python packages can now be installed using pip.
Ubuntu should now include the venv module. You can verify it by running:
python3 -m venv --help
If the help page appears without errors, your server is ready to create isolated Python environments for Django projects.
One of the most common mistakes beginners make is installing Python packages globally. While it may seem convenient, global installations can create dependency conflicts when multiple projects require different package versions.
A virtual environment solves this problem by giving each Django project its own isolated Python environment.
This means:
Using a virtual environment is considered a standard best practice for Django development and production deployments.
Before moving on, confirm that the essential tools are installed correctly.
Run the following commands:
python3 --version
pip3 --version
If both commands return version information without errors, your server is fully prepared for the next stage.
In the next step, you’ll create a dedicated project directory, set up a Python virtual environment, and activate it before installing Django and your project’s dependencies.
Now that Python and its required packages are installed, it’s time to create a virtual environment for your Django project. A virtual environment isolates your project’s dependencies from the system wide Python installation, making it easier to manage packages and avoid version conflicts.
Whether you’re deploying a personal project or a production application, using a virtual environment is considered a best practice for Django deployments.
First, create a directory where your Django project will be stored. You can choose any location, but many developers keep application files inside their home directory.
For example:
mkdir ~/django-project
Move into the newly created directory:
cd ~/django-project
If you’ve already uploaded or cloned your Django project into another directory, navigate to that location instead.
Inside your project directory, create a virtual environment using Python’s built in venv module.
python3 -m venv venv
This command creates a new folder named venv, which contains an isolated Python interpreter along with its own package manager and libraries.
Your project structure will now look similar to this:
django-project/
├── venv/
At this stage, no Python packages have been installed yet.
Before installing Django or any project dependencies, activate the virtual environment.
Run:
source venv/bin/activate
After activation, your terminal prompt will change slightly:
(venv) username@ubuntu:~/django-project$
The (venv) prefix indicates that all Python and pip commands now operate inside the virtual environment instead of the system-wide installation.
Before installing project packages, it’s a good idea to update pip to the latest available version.
pip install --upgrade pip
Keeping pip updated helps avoid compatibility issues with newer Python packages and ensures smoother dependency installation.
To confirm that the virtual environment is active, run:
which python
Example output:
/home/username/django-project/venv/bin/python
Similarly, verify pip:
which pip
Example output:
/home/username/django-project/venv/bin/pip
If both commands point to the venv directory, the virtual environment has been activated successfully.
Whenever you finish working on the project, you can exit the virtual environment by running:
deactivate
Your terminal prompt will return to its normal state.
Don’t worry, you can reactivate the environment at any time using:
source venv/bin/activate
Creating a virtual environment may seem like a small step, but it plays an important role in maintaining a stable production environment. It keeps your Django project self contained, prevents package conflicts, and makes future updates much easier.
This approach is especially useful if you plan to host multiple Python applications on the same VPS, as each project can maintain its own dependencies without affecting the others.
With the virtual environment ready, the server is now prepared to install Django, Gunicorn, and all the libraries your application requires. In the next step, we’ll install the project dependencies and verify that everything is configured correctly before deploying the application.
With your virtual environment active, the next step is to install Django and all the packages your application needs to run. Since every Django project may use different libraries, it’s important to install the exact dependencies required by your application instead of relying on the system wide Python installation.
If you’re deploying an existing project, you should always install packages from its requirements.txt file. This ensures your VPS uses the same package versions as your development environment.
Before installing anything, verify that your virtual environment is active.
If it isn’t, activate it using:\
source venv/bin/activate
Your terminal prompt should display something similar to:
(venv) username@ubuntu:~/django-project$
If you’re creating a new Django project or simply want to install Django manually, run:
pip install django
Once the installation is complete, verify it by checking the installed version:
django-admin --version
Example output:
5.2.x
The version number may differ depending on the latest stable Django release available when you perform the installation.
Gunicorn is a production grade WSGI server that runs your Django application. Unlike Django’s built in development server, Gunicorn is designed to handle production traffic efficiently.
Install it with:
pip install gunicorn
Verify the installation:
gunicorn --version
You should see the installed Gunicorn version displayed in the terminal.
If your Django project already contains a requirements.txt file, install all required packages using:
pip install -r requirements.txt
This command installs every dependency listed in the file, including Django (if specified), database drivers, authentication libraries, API packages, and any third party modules your application depends on.
Using requirements.txt keeps deployments consistent and reduces the risk of missing packages.
If you’re deploying a project that doesn’t already include a requirements.txt file, you can generate one after installing all required packages.
Run:
pip freeze > requirements.txt
This saves all installed Python packages and their versions into a file, making future deployments much easier.
To view the packages currently installed inside your virtual environment, run:
pip list
You’ll see output similar to:
Django
gunicorn
pip
setuptools
wheel
...
The list will vary depending on the dependencies used by your project.
Before continuing, it’s a good idea to verify that all installed packages are compatible.
Run:
pip check
If everything is installed correctly, you’ll see a message similar to:
No broken requirements found.
If any dependency conflicts appear, resolve them before moving to the next step to avoid runtime errors later.
A Django application often relies on multiple third party packages for features such as database connectivity, authentication, REST APIs, image processing, caching, or background tasks. Installing these dependencies inside the virtual environment ensures your application runs in a predictable and isolated environment.
It also makes future updates, troubleshooting, and deployments much simpler, especially when working across multiple servers or collaborating with a development team.
At this point, your Python environment is fully prepared, and all required packages are installed. In the next step, you’ll upload or clone your Django project onto the VPS and prepare it for production configuration.
Now that your Python environment is ready, it’s time to place your Django project on the VPS. There are two common ways to do this:
If you’re actively developing your application, using Git makes future updates much easier. However, if your project isn’t stored in a Git repository, you can upload the files directly to the server.
If Git isn’t installed on your VPS yet, install it first:
sudo apt install git -y
Verify the installation:
git --version
Next, navigate to the directory where you want to store your project.
cd ~/django-project
Clone your repository:
git clone https://github.com/your-username/your-repository.git
After cloning, move into the project folder:
cd your-repository
Replace the repository URL and folder name with your own project details.
If your project isn’t hosted on GitHub, GitLab, or another Git platform, upload it directly from your computer using an SFTP client such as FileZilla or WinSCP, or use the scp command.
For example:
scp -r my-django-project username@your_server_ip:~/django-project/
Once the upload finishes, connect to your VPS and navigate to the project directory:
cd ~/django-project/my-django-project
Before moving forward, make sure all project files were copied correctly.
Run:
ls
You should see files similar to:
manage.py
requirements.txt
app/
project/
static/
templates/
The exact folders will vary depending on your Django project’s structure.
If you aren’t already inside the virtual environment, activate it again.
source ../venv/bin/activate
If your virtual environment is located elsewhere, adjust the path accordingly.
Your terminal should now display:
(venv) username@ubuntu:~/django-project/your-repository$
If you haven’t already installed the dependencies from your project’s requirements.txt file, do so now.
pip install -r requirements.txt
This ensures every library required by your application is available before deployment.
Run the following command from the directory containing manage.py:
python manage.py check
If everything is configured correctly, Django will display:
System check identified no issues (0 silenced).
If errors appear, resolve them before continuing. Common issues at this stage include missing dependencies, incorrect project paths, or missing environment variables.
Before configuring Django for production, verify you’re working from the correct location.
Run:
pwd
Example output:
/home/username/django-project/your-repository
This directory will be referenced later when configuring Gunicorn and the systemd service, so it’s helpful to note its exact path.
Successfully copying your project to the VPS is more than just transferring files. This step confirms that your application, dependencies, and project structure are all in place before you begin production configuration.
Taking a few minutes to verify the directory structure and run Django’s built in system checks can save significant troubleshooting time later, especially when configuring Gunicorn, Nginx, and system services.
With your project now available on the server, the next step is to configure Django for a production environment. You’ll disable development settings, configure allowed hosts, prepare static files, and run database migrations to make the application ready for public access.
At this stage, your Django project is available on the VPS, but it’s still configured for development. Before making the application publicly accessible, you need to update a few settings to improve security, stability, and performance.
A production configuration ensures your application runs safely behind Gunicorn and Nginx while preventing sensitive information from being exposed.
Navigate to your project’s settings file. Depending on your project structure, it’s usually located here:
your-project/
├── manage.py
├── your_project/
│ ├── settings.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
Open the file using Nano:
nano your_project/settings.py
Replace your_project with your actual Django project name.
During development, Django displays detailed error pages that are useful for debugging. However, exposing these details on a live server can reveal sensitive information about your application.
Locate:
DEBUG = True
Change it to:
DEBUG = False
Never leave DEBUG enabled on a production server.
Django only accepts requests from domains or IP addresses listed in the ALLOWED_HOSTS setting.
Update it with your server’s IP address or domain name.
Example:
ALLOWED_HOSTS = [
"yourdomain.com",
"www.yourdomain.com",
"203.0.113.10",
]
If you’re testing before connecting a domain, using the VPS IP address is perfectly acceptable.
Production deployments use Nginx to serve static files such as CSS, JavaScript, and images.
Inside settings.py, verify or add the following:
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
If your project already contains a STATICFILES_DIRS setting, leave it unchanged unless you need to modify the directory structure.
If users can upload images, documents, or other files, configure the media settings as well.
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
Later, you’ll configure Nginx to serve this directory.
Every Django project contains a unique SECRET_KEY used for cryptographic signing. Avoid hardcoding it into your project, especially if the code is stored in a public repository.
Instead of:
SECRET_KEY = "your-secret-key"
Store it as an environment variable.
Example:
import os
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
Then export it on your server:
export DJANGO_SECRET_KEY="your-secret-key"
For long term deployments, it’s better to load environment variables using tools such as python-dotenv or configure them directly in the Gunicorn systemd service.
If you’re using SQLite for a small project or testing, no additional changes may be required.
For production applications using PostgreSQL or MySQL, verify that your database settings contain the correct:
Always confirm that the database server is running before proceeding.
After updating your settings, apply all pending migrations.
Run:
python manage.py migrate
If successful, Django will create or update the required database tables.
Next, gather all static assets into the STATIC_ROOT directory.
Run:
python manage.py collectstatic
When prompted, type:
yes
Django will copy all static files into the production directory that Nginx will serve later.
Before continuing, use Django’s built in system check.
python manage.py check --deploy
If everything is configured correctly, Django will report any security recommendations or confirm that your production settings are ready.
Pay close attention to any warnings. Resolving them now helps prevent deployment issues later.
Before introducing Gunicorn and Nginx, verify that Django still starts correctly.
Run:
python manage.py runserver 0.0.0.0:8000
Open your browser and visit:
http://your_server_ip:8000
If your homepage loads successfully, your Django project is functioning correctly.
Press Ctrl + C to stop the development server after confirming everything works.
Important: The Django development server is only for testing. It should never be used to host a production website. In the next step, you’ll replace it with Gunicorn, which is designed to serve Django applications reliably in production.
Before configuring Gunicorn as a permanent system service, it’s a good idea to run it manually and verify that your Django application starts correctly. This helps identify configuration issues early, making it easier to troubleshoot before introducing Nginx and systemd.
If Gunicorn can successfully serve your application at this stage, the remaining deployment process becomes much smoother.
Navigate to the folder that contains your manage.py file.
For example:
cd ~/django-project/your-repository
If your virtual environment isn’t active, activate it again:
source ../venv/bin/activate
Adjust the path if your virtual environment is located elsewhere.
Gunicorn needs Django’s WSGI application to start the project.
Your project structure will usually look similar to this:
your-project/
├── manage.py
└── your_project/
├── settings.py
├── urls.py
├── wsgi.py
└── asgi.py
The important file here is:
your_project/wsgi.py
The module name before .wsgi will be used when starting Gunicorn.
For example:
your_project.wsgi:application
Replace your_project with your actual Django project name throughout this guide.
Run the following command:
gunicorn --bind 0.0.0.0:8000 your_project.wsgi:application
If everything is configured correctly, you’ll see output similar to:
[INFO] Starting gunicorn
[INFO] Listening at: http://0.0.0.0:8000
[INFO] Booting worker with pid: 12345
This means Gunicorn has started successfully and is serving your Django application.
Open your web browser and visit:
http://your_server_ip:8000
If your website loads correctly, Gunicorn is communicating with Django as expected.
Try navigating through a few pages to confirm that:
Testing these basic functions now can help detect configuration problems before moving on.
If the application doesn’t start, Gunicorn usually displays an error message in the terminal.
Some common issues include:
Read the first error carefully, it often points directly to the root cause.
You can also run:
python manage.py check
to verify that Django detects no configuration issues.
After confirming that your application works, stop Gunicorn by pressing:
Ctrl + C
This is expected because Gunicorn is currently running in the foreground for testing purposes.
In the next step, you’ll configure Gunicorn to run as a background service using systemd, allowing it to:
Running Gunicorn manually helps separate application issues from server configuration problems. If something goes wrong later when setting up the systemd service or configuring Nginx, you’ll already know that the Django application itself is working correctly.
This small verification step can save considerable troubleshooting time and provides a solid foundation before completing the production deployment.
After confirming that your Django application runs correctly with Gunicorn, the next step is to configure it as a systemd service. This allows Gunicorn to run in the background, start automatically whenever the VPS reboots, and restart itself if it unexpectedly stops.
Using systemd is the standard approach for running Django applications in production because it provides reliable service management and makes monitoring much easier.
Before creating the service file, note the following details from your server:
For example:
| Setting | Example |
| Username | ubuntu |
| Project Directory | /home/ubuntu/django-project/myproject |
| Virtual Environment | /home/ubuntu/django-project/venv |
| Django Project Name | myproject |
You’ll use these values in the service configuration below.
Create a new systemd service file:
sudo nano /etc/systemd/system/gunicorn.service
Paste the following configuration:
[Unit]
Description=Gunicorn daemon for Django
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/django-project/myproject
Environment="PATH=/home/ubuntu/django-project/venv/bin"
ExecStart=/home/ubuntu/django-project/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/gunicorn.sock \
myproject.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
Before saving the file, replace:
Using a Unix socket (/run/gunicorn.sock) instead of a TCP port allows Nginx to communicate with Gunicorn more efficiently.
Save the file and exit Nano.
Whenever a new service file is created or modified, reload the systemd configuration:
sudo systemctl daemon-reload
This command allows Ubuntu to recognize the newly created Gunicorn service.
Start Gunicorn:
sudo systemctl start gunicorn
If no errors appear, the service has started successfully.
To make sure Gunicorn starts automatically after every VPS reboot, enable the service:
sudo systemctl enable gunicorn
Ubuntu will create the necessary symbolic links so the service launches during system startup.
Verify that Gunicorn is running:
sudo systemctl status gunicorn
A healthy service typically displays output similar to:
● gunicorn.service - Gunicorn daemon for Django
Loaded: loaded
Active: active (running)
The important part is:
Active: active (running)
If you see this status, Gunicorn is running correctly in the background.
Press Q to exit the status screen.
Confirm that the Unix socket has been created:
ls -l /run/gunicorn.sock
If the service is working properly, you’ll see the socket file listed.
This socket will be used by Nginx in the next step to forward incoming HTTP requests to your Django application.
If the service doesn’t start, review its logs to identify the cause.
View recent log entries:
sudo journalctl -u gunicorn
To monitor logs in real time while troubleshooting:
sudo journalctl -u gunicorn -f
These logs often reveal issues such as:
Resolving these issues before configuring Nginx will make the remaining deployment process much smoother.
As you maintain your application, you’ll frequently use these commands:
Restart the service:
sudo systemctl restart gunicorn
Stop the service:
sudo systemctl stop gunicorn
Start it again:
sudo systemctl start gunicorn
Check its current status:
sudo systemctl status gunicorn
These commands are especially useful after deploying new code or updating project dependencies.
Running Gunicorn manually works for testing, but it’s not practical for a production server. A systemd service keeps your application running continuously, restarts it automatically if it crashes, and ensures it comes back online after a system reboot.
With Gunicorn now running as a managed background service, your Django application is ready to receive requests. In the next step, you’ll configure Nginx as a reverse proxy to handle incoming traffic, serve static files efficiently, and forward application requests to Gunicorn through the Unix socket.
With Gunicorn running as a background service, the next step is to configure Nginx. Instead of exposing Gunicorn directly to the internet, Nginx acts as a reverse proxy that receives incoming HTTP and HTTPS requests, serves static files efficiently, and forwards dynamic requests to your Django application through the Gunicorn socket.
This architecture is widely used for Django production deployments because it improves performance, enhances security, and provides greater flexibility for future scaling.
If Nginx isn’t already installed on your VPS, install it using the following command:
sudo apt install nginx -y
After the installation completes, verify that Nginx is running:
sudo systemctl status nginx
If the service is active, you’ll see output similar to:
Active: active (running)
If Nginx isn’t running, start it manually:
sudo systemctl start nginx
You can also enable it to start automatically whenever the server reboots:
sudo systemctl enable nginx
Instead of modifying the default configuration, create a separate server block for your Django application.
Create a new configuration file:
sudo nano /etc/nginx/sites-available/myproject
Replace myproject with a meaningful name for your website.
Paste the following configuration into the file:
server {
listen 80;
server_name yourdomain.com www.yourdomain.com your_server_ip;
location = /favicon.ico {
access_log off;
log_not_found off;
}
location /static/ {
alias /home/ubuntu/django-project/myproject/staticfiles/;
}
location /media/ {
alias /home/ubuntu/django-project/myproject/media/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Before saving the file, replace:
If your project doesn’t use uploaded media files, you can remove the /media/ block.
Create a symbolic link so Nginx loads the new configuration.
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
If the default Nginx site is still enabled, you can disable it to avoid configuration conflicts:
sudo rm /etc/nginx/sites-enabled/default
Before restarting the service, always test the configuration for syntax errors.
Run:
sudo nginx -t
If everything is correct, you’ll see:
nginx: the configuration file syntax is ok
nginx: configuration file test is successful
If any errors appear, fix them before proceeding.
Apply the new configuration by restarting Nginx.
sudo systemctl restart nginx
Alternatively, you can reload the configuration without interrupting existing connections:
sudo systemctl reload nginx
Open your browser and visit:
http://your_server_ip
or, if you’ve already configured DNS: http://yourdomain.com
If everything is configured correctly, your Django application should now load through Nginx instead of the Django development server or the manually started Gunicorn process.
Navigate through your website and confirm that static assets load correctly.
Check that:
If the website loads but appears unstyled, it’s usually because:
Correcting these issues should restore the missing assets.
While managing your server, these commands are commonly used:
Check service status:
sudo systemctl status nginx
Restart Nginx:
sudo systemctl restart nginx
Reload the configuration:
sudo systemctl reload nginx
Stop Nginx:
sudo systemctl stop nginx
View recent error logs:
sudo tail -f /var/log/nginx/error.log
These commands are particularly useful when troubleshooting configuration issues or after making changes to your server block.
Although Gunicorn runs your Django application, it isn’t designed to serve static assets efficiently or manage client connections on its own. Nginx complements Gunicorn by handling these responsibilities.
Using Nginx provides several important advantages:
With Nginx now forwarding requests to Gunicorn, your Django application is running through a production ready web server. In the next step, you’ll configure the UFW firewall to allow HTTP and HTTPS traffic while keeping your VPS secure.
A firewall is one of the simplest ways to improve your server’s security. By default, it blocks unwanted incoming connections and allows only the services you explicitly permit. On Ubuntu, the recommended firewall is UFW (Uncomplicated Firewall), which provides an easy way to manage firewall rules from the command line.
Before making your Django application publicly accessible, configure UFW to allow web traffic while keeping unnecessary ports closed.
Important: If you’re connected to your VPS through SSH, always allow SSH access before enabling the firewall. Otherwise, you could lock yourself out of the server.
Most Ubuntu installations include UFW by default. You can verify this by running:
sudo ufw status
If UFW isn’t installed, install it with:
sudo apt install ufw -y
Before enabling the firewall, make sure SSH access is allowed.
sudo ufw allow OpenSSH
Verify the rule:
sudo ufw status
You should see an entry similar to:
OpenSSH ALLOW Anywhere
Since your Django application is being served through Nginx, allow both HTTP (port 80) and HTTPS (port 443).
The easiest way is to use the predefined Nginx profile:
sudo ufw allow 'Nginx Full'
This opens:
If you only want to allow HTTP temporarily, you can use:
sudo ufw allow 'Nginx HTTP'
Later, after installing an SSL certificate, you can switch to the Nginx Full profile.
Once the required rules are in place, enable UFW.
sudo ufw enable
When prompted, type:
y
and press Enter.
The firewall is now active and protecting your server.
Check the current firewall configuration:
sudo ufw status
A typical configuration looks similar to:
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
This confirms that SSH, HTTP, and HTTPS traffic are permitted while other unsolicited connections remain blocked.
Open your browser and visit:
http://your_server_ip
or
http://yourdomain.com
If your website loads normally, the firewall is configured correctly.
If the site is unreachable, double check that:
As you continue managing your VPS, these commands are particularly useful.
View all firewall rules:
sudo ufw status numbered
Allow a specific port:
sudo ufw allow 8000
Delete a firewall rule:
sudo ufw delete allow 8000
Disable the firewall temporarily:
sudo ufw disable
Reload firewall rules:
sudo ufw reload
In most production environments, you won’t need to open additional ports unless you’re running other services such as a database, mail server, or monitoring tools.
Even if your Django application is configured correctly, leaving unnecessary ports open can expose your server to unwanted traffic and automated attacks. A properly configured firewall reduces this risk by limiting access to only the services your application actually needs.
With UFW configured, your VPS is now better protected while remaining fully accessible to website visitors. In the next step, you’ll point your domain name to the VPS, allowing users to access your Django application through a custom domain instead of the server’s IP address.
At this point, your Django application is accessible through your domain over HTTP. The final step before going live is to enable HTTPS by installing an SSL certificate.
HTTPS encrypts the communication between your visitors and your server, protecting sensitive information such as login credentials, contact forms, and session cookies. It also improves user trust, and modern browsers mark websites without HTTPS as “Not Secure.”
One of the easiest ways to enable HTTPS on Ubuntu is by using Let’s Encrypt together with Certbot.
Prerequisite: Before continuing, make sure your domain already points to your VPS and your website is accessible over HTTP.
First, update the package list:
sudo apt update
Now install Certbot and its Nginx plugin:
sudo apt install certbot python3-certbot-nginx -y
The Nginx plugin automatically detects your website configuration and updates it to use HTTPS.
Before requesting an SSL certificate, test your Nginx configuration.
sudo nginx -t
If everything is correct, you’ll see:
nginx: configuration file syntax is ok
nginx: configuration file test is successful
If errors appear, resolve them before continuing.
Run the following command:
sudo certbot --nginx
Certbot will guide you through a few prompts.
You may be asked to:
Choose your domain from the list and continue.
During the installation, Certbot usually asks:
Redirect HTTP traffic to HTTPS?
Choose the option that redirects all HTTP requests to HTTPS.
This ensures that visitors who enter: http://yourdomain.com
are automatically redirected to: https://yourdomain.com
without requiring any manual action.
After the installation completes successfully, open your browser and visit: https://yourdomain.com
Verify that:
Your Django application is now protected with an SSL certificate.
You can inspect the installed certificate using:
sudo certbot certificates
The output displays:
This is useful when managing multiple websites on the same VPS.
Let’s Encrypt certificates are valid for 90 days, but Certbot can renew them automatically.
To test the renewal process without making any changes, run:
sudo certbot renew --dry-run
If the simulation completes successfully, automatic renewal is configured correctly.
Ubuntu usually installs a system timer that checks for certificate renewals automatically.
Verify it using:
systemctl list-timers
You should see a Certbot timer in the list.
This means your SSL certificate will renew automatically before it expires, provided the domain remains reachable.
Visit your website and verify the following:
If any assets still load over HTTP, update their URLs to use HTTPS or relative paths to eliminate mixed content warnings.
Running your Django application over HTTPS is no longer optional—it’s an expected standard for modern websites. Beyond encrypting traffic, HTTPS helps protect user data, builds visitor confidence, and ensures compatibility with many modern web features and browser security requirements.
With Gunicorn, Nginx, UFW, and SSL now configured, your Django application is fully deployed and ready for production use. In the next section, you’ll perform a final deployment checklist to verify that every component is working correctly before launching your website to the public.
Congratulations! Your Django application is now deployed on your Ubuntu VPS with Gunicorn, Nginx, and HTTPS configured. Before considering the deployment complete, take a few minutes to verify that every component is working as expected.
Performing these checks can help you catch small issues before they affect your users.
Open your browser and visit your domain:
https://yourdomain.com
Verify that:
If your application includes user authentication, sign in and test the login process.
Look for the padlock icon in your browser’s address bar.
You should also verify that:
A secure HTTPS connection confirms that your SSL certificate has been installed correctly.
Navigate through multiple pages and ensure that all static assets load correctly.
Check for:
If the website appears unstyled, rerun:
python manage.py collectstatic
Then reload Nginx:
sudo systemctl reload nginx
If your Django project allows users to upload files, test that functionality as well.
For example:
If media files don’t load, review your MEDIA_ROOT, MEDIA_URL, and the corresponding Nginx location /media/ configuration.
Confirm that Gunicorn is running normally.
sudo systemctl status gunicorn
A healthy service should display:
Active: active (running)
If the service isn’t running, review the logs:
sudo journalctl -u gunicorn -f
Verify that Nginx is also active.
sudo systemctl status nginx
Expected output:
Active: active (running)
If necessary, restart it:
sudo systemctl restart nginx
Check your firewall configuration.
sudo ufw status
You should see rules allowing:
No additional ports should be open unless they’re required by another service running on your VPS.
Perform a few actions inside your application that interact with the database.
Examples include:
If these operations work without errors, your database configuration is functioning correctly.
Visit:
https://yourdomain.com/admin
Log in using your administrator account and verify that:
Even if everything appears to work correctly, it’s worth checking the logs for hidden warnings or errors.
View Gunicorn logs:
sudo journalctl -u gunicorn --since "30 minutes ago"
View Nginx error logs:
sudo tail -f /var/log/nginx/error.log
Reviewing these logs after deployment can help identify issues before they become noticeable to users.
Before launching your Django application publicly, confirm that the following items are complete:
If every item on this checklist is complete, your Django application is running in a stable production environment and is ready to serve real users.
Even when you carefully follow each deployment step, it’s normal to encounter a few issues the first time you deploy a Django application. Most problems are caused by small configuration mistakes, such as an incorrect file path, missing dependency, or server setting.
The good news is that these errors are usually easy to diagnose once you know where to look. Below are some of the most common Django deployment issues along with their likely causes and practical solutions.
A 502 Bad Gateway error usually means that Nginx cannot communicate with Gunicorn.
First, check the Gunicorn service status:
sudo systemctl status gunicorn
If the service isn’t running, restart it:
sudo systemctl restart gunicorn
Next, verify that the socket file exists:
ls -l /run/gunicorn.sock
If the socket isn’t present, review the Gunicorn logs:
sudo journalctl -u gunicorn -f
Most 502 errors can be resolved by correcting the Gunicorn configuration or fixing startup errors reported in the logs.
If your website loads but appears unstyled, Django’s static files aren’t being served correctly.
Collect all static files again:
python manage.py collectstatic
Then reload Nginx:
sudo systemctl reload nginx
Also verify that:
Sometimes Gunicorn refuses to start after creating the systemd service.
Review the service logs:
sudo journalctl -u gunicorn -f
Double-check:
After making changes, reload systemd:
sudo systemctl daemon-reload
Restart the service:
sudo systemctl restart gunicorn
If Django displays a DisallowedHost exception, it means the incoming request isn’t included in the ALLOWED_HOSTS setting.
Open your settings file:
nano your_project/settings.py
Update the configuration:
ALLOWED_HOSTS = [
"yourdomain.com",
"www.yourdomain.com",
"203.0.113.10",
]
Restart Gunicorn after saving the file:
sudo systemctl restart gunicorn
This error appears when Django cannot locate one of your Python packages.
Activate the virtual environment:
source venv/bin/activate
Install the missing package:
pip install package-name
If you’re deploying an existing project, reinstall all dependencies:
pip install -r requirements.txt
Permission-related errors usually occur when Gunicorn or Nginx cannot access project files.
Verify file ownership:
ls -l
Update ownership if necessary:
sudo chown -R $USER:www-data /home/username/django-project
Then grant appropriate read and execute permissions:
sudo chmod -R 755 /home/username/django-project
Avoid using overly permissive settings such as 777, as they can introduce unnecessary security risks.
Sometimes the application starts, but database tables are missing.
Apply all pending migrations:
python manage.py migrate
If you’re unsure which migrations have been applied:
python manage.py showmigrations
This command displays the migration status for every installed Django app.
If HTTPS doesn’t work correctly, first verify that your domain points to the correct VPS.
Check your certificate:
sudo certbot certificates
Then test automatic renewal:
sudo certbot renew --dry-run
If renewal succeeds, your SSL configuration is working properly.
If Nginx fails to restart after editing its configuration, test the syntax before making further changes.
sudo nginx -t
If errors are reported, correct the line mentioned in the output before restarting Nginx.
Once the configuration passes validation, reload the service:
sudo systemctl reload nginx
When something doesn’t work, avoid changing multiple settings at once. Instead, check each layer of the deployment individually:
Following this sequence makes it much easier to identify the source of the problem and resolve it without unnecessary trial and error.
With these troubleshooting tips, you’ll be able to diagnose and fix the vast majority of deployment issues encountered when hosting a Django application on an Ubuntu VPS. In the next section, we’ll cover Django deployment best practices to help you improve your server’s security, maintainability, and long-term performance.
Deploying your Django application successfully is only the beginning. To keep your website secure, stable, and easy to maintain over time, it’s important to follow a few production best practices. These recommendations can help reduce downtime, improve performance, and make future updates much smoother.
Regular software updates include security patches, bug fixes, and performance improvements. Make it a habit to update your server periodically.
sudo apt update
sudo apt upgrade -y
Likewise, keep your Python dependencies up to date after testing them in a development environment.
pip list --outdated
Avoid upgrading packages directly on a production server without verifying compatibility first.
Installing Python packages globally can lead to dependency conflicts, especially if you’re hosting multiple Python applications on the same VPS.
Using a virtual environment keeps each project isolated, making it easier to manage dependencies and reproduce your deployment on another server.
The Django debug page contains detailed information about your application, including file paths and configuration details. Exposing this information on a public server creates unnecessary security risks.
Always ensure your production configuration contains:
DEBUG = False
Before launching your website, confirm that the setting hasn’t accidentally been changed during development.
Avoid hardcoding sensitive information directly into your source code.
Examples include:
Keeping sensitive values in environment variables makes your application more secure and simplifies deployment across different environments.
Even if your Django application doesn’t process payments or collect sensitive personal information, HTTPS should always be enabled.
A valid SSL certificate:
If you’re using Let’s Encrypt, periodically verify that automatic certificate renewal continues to work correctly.
Unexpected problems such as accidental file deletion, server failures, or software issues can occur at any time.
Create regular backups of:
Many VPS providers also offer automated snapshots, which provide an additional layer of protection.
Logs provide valuable information about your application’s health and can help identify problems before they affect users.
Useful log locations include:
Gunicorn:
sudo journalctl -u gunicorn -f
Nginx errors:
sudo tail -f /var/log/nginx/error.log
Reviewing logs regularly makes it easier to detect configuration issues, failed requests, and unexpected application errors.
Since SSH provides administrative access to your VPS, securing it should be a priority.
Some recommended practices include:
These simple measures significantly reduce the risk of unauthorized access.
As your project grows, managing dependencies becomes increasingly important.
Whenever you install or update a package, regenerate your dependency list:
pip freeze > requirements.txt
This ensures future deployments install the exact package versions your application expects.
Avoid uploading untested code directly to your production server.
A safer workflow is:
This approach minimizes downtime and reduces the chance of introducing unexpected issues.
As traffic increases, monitor your VPS to ensure it has sufficient resources.
Useful commands include:
View CPU and memory usage:
top
Check disk usage:
df -h
View memory usage:
free -h
Monitoring these metrics helps you determine when it’s time to upgrade your VPS or optimize your application.
Whenever you modify Django settings, update dependencies, or change server configurations, restart the affected services so the changes take effect.
For example:
Restart Gunicorn:
sudo systemctl restart gunicorn
Reload Nginx:
sudo systemctl reload nginx
Verifying that both services restart successfully should always be part of your deployment routine.
A reliable Django deployment is built on more than just installing Gunicorn and Nginx. Keeping your server updated, securing sensitive information, monitoring system health, and following established deployment practices will help your application remain stable as it grows.
By following the steps and best practices covered in this guide, you’ll have a production-ready Django deployment that’s easier to maintain, more secure, and better prepared to handle real world traffic.
Deploying a Django application on a VPS may seem challenging at first, but once you understand the deployment workflow, the process becomes much more manageable. By preparing your Ubuntu server, creating a virtual environment, installing your project dependencies, configuring Gunicorn and Nginx, enabling the firewall, connecting your domain, and securing the application with HTTPS, you’ve built a production-ready environment that follows widely accepted deployment practices.
Remember that deployment isn’t a one-time task. Regular updates, monitoring, backups, and security checks are essential for keeping your application reliable over the long term. As your project grows, you can further enhance your setup with tools such as caching, database optimization, background task queues, and automated CI/CD pipelines.
With your Django application now live, you can confidently focus on developing new features and improving the user experience while knowing your deployment is built on a solid foundation.
Yes, but it’s not recommended for production. Django’s built-in development server is intended only for local development and testing. For a production environment, use a WSGI server such as Gunicorn behind Nginx to ensure better performance, stability, and security.
Nginx acts as a reverse proxy. It handles incoming client requests, serves static and media files efficiently, manages HTTPS connections, and forwards application requests to Gunicorn, allowing Django to focus solely on processing application logic.
Yes. Ubuntu 24.04 LTS is an excellent choice for deploying Django applications and is fully compatible with Python, Gunicorn, Nginx, and other commonly used deployment tools.
For small personal projects or development environments, 1 GB RAM is usually sufficient. For production websites, 2 GB RAM or more is recommended, especially if your application serves multiple users or uses PostgreSQL, Redis, or background workers.
Yes. Enabling HTTPS protects data exchanged between users and your server, improves trust, and is considered a standard security practice for modern websites.
After deploying new code or changing your application settings, restart the Gunicorn service using:
sudo systemctl restart gunicorn
This ensures Gunicorn loads the latest version of your application.
This usually happens when collectstatic hasn’t been run, STATIC_ROOT is configured incorrectly, or the Nginx alias path doesn’t match your Django settings. Verify these configurations and reload Nginx after making any changes.
Yes. You can host multiple Django applications on a single VPS by creating separate virtual environments, Gunicorn services, and Nginx server blocks for each project. This keeps each application isolated while allowing them to share the same server resources.