Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

Linux & Mac & Bash Command Dictionary

  1. Home
  2. Linux & Mac & Bash Command Dictionary
  3. Deploying Django Apps (Gunicorn + Nginx)

Deploying Django Apps (Gunicorn + Nginx)

"Deploying a Django App (Gunicorn + Nginx)" is the standard procedure for publishing a Python Django web application to a production environment. It uses virtualenv to isolate the Python environment, and Gunicorn — a WSGI server — as the application server. Registering Gunicorn as a systemd service enables automatic startup on OS boot and automatic restart on crashes. Placing Nginx in front as a reverse proxy provides fast static file delivery, SSL termination, and public access on ports 80 and 443.

Syntax

# ===============================================
#  Django Deployment Overview
# ===============================================

# -----------------------------------------------
#  [Step 1] Create a Python virtual environment with virtualenv
# -----------------------------------------------

# Install python3-venv (skip if already installed)
sudo apt install python3-venv

# Move to your project directory
# This example uses "okabe-lab" as the project name
cd /var/www/okabe-lab

# Create the virtual environment (a directory named "venv" will be created)
python3 -m venv venv

# Activate the virtual environment
# You are in the virtual environment when (venv) appears at the start of your prompt
source venv/bin/activate

# Install Django and Gunicorn
pip install django gunicorn

# -----------------------------------------------
#  [Step 2] Update Django settings for production
# -----------------------------------------------

# Key production settings in settings.py
# DEBUG = False
# ALLOWED_HOSTS = ['example.com', 'www.example.com']
# STATIC_ROOT = '/var/www/okabe-lab/staticfiles/'

# Collect static files into STATIC_ROOT
python manage.py collectstatic

# Apply database migrations
python manage.py migrate

# -----------------------------------------------
#  [Step 3] Test Gunicorn manually
# -----------------------------------------------

# Start Gunicorn manually to verify it works
# Use the format {project_name}.wsgi:application
# --bind specifies the path to the Unix socket file
gunicorn --workers 3 --bind unix:/run/okabe-gunicorn.sock okabe_lab.wsgi:application

# Press Ctrl+C to stop after confirming it works

# -----------------------------------------------
#  [Step 4] Register as a systemd service
# -----------------------------------------------

# Create /etc/systemd/system/okabe-gunicorn.service (see "Examples" below)

# Reload the daemon configuration
sudo systemctl daemon-reload

# Start the service and enable it to start automatically on boot
sudo systemctl start okabe-gunicorn
sudo systemctl enable okabe-gunicorn

# -----------------------------------------------
#  [Step 5] Configure the Nginx reverse proxy
# -----------------------------------------------

# Create /etc/nginx/sites-available/okabe-django.conf (see "Examples" below)

# Create a symbolic link in sites-enabled to activate the configuration
sudo ln -s /etc/nginx/sites-available/okabe-django.conf \
           /etc/nginx/sites-enabled/

# Verify the Nginx configuration syntax, then reload
sudo nginx -t
sudo systemctl reload nginx

Syntax Reference

StepDescription
Create virtual environmentpython3 -m venv venv creates a Python environment dedicated to your project. It keeps your system Python untouched and makes package version management easier.
Activate virtual environmentsource venv/bin/activate activates the virtual environment. It is active when (venv) appears at the start of your prompt.
Install packagespip install django gunicorn installs Django and Gunicorn inside the virtual environment. It is recommended to record dependencies with pip freeze > requirements.txt.
Collect static filespython manage.py collectstatic collects Django's static files into STATIC_ROOT. This step is required in production because Nginx serves them directly.
DB migrationpython manage.py migrate applies the latest database schema. Make it a habit to check for pending migrations on every deployment.
Test Gunicorn manuallygunicorn --workers 3 --bind unix:/run/okabe-gunicorn.sock {project}.wsgi:application verifies the setup. Always confirm it starts successfully before registering with systemd.
Create systemd serviceCreate a unit file at /etc/systemd/system/{service_name}.service. Set the absolute paths for your project in both WorkingDirectory and ExecStart.
Reload daemonsudo systemctl daemon-reload makes systemd recognize changes to a unit file. Always run this after editing a service file.
Start and enable servicesudo systemctl start {service_name} starts the service, and sudo systemctl enable {service_name} registers it for automatic startup on boot.
Create Nginx config fileCreate a configuration file in /etc/nginx/sites-available/ and specify the Gunicorn Unix socket with proxy_pass.
Enable Nginx configsudo ln -s /etc/nginx/sites-available/{config_file} /etc/nginx/sites-enabled/ creates a symbolic link to activate the configuration.
Validate and reload Nginxsudo nginx -t checks for syntax errors, then sudo systemctl reload nginx applies the configuration.

Examples

Gunicorn systemd service configuration
# -----------------------------------------------
#  /etc/systemd/system/okabe-gunicorn.service
# -----------------------------------------------
#  A systemd unit file that starts the Django project "okabe-lab"
#  (Rintaro Okabe's project) via Gunicorn

[Unit]
# A description of the service (shown in journalctl and other tools)
Description=Gunicorn daemon for okabe-lab (Rintaro Okabe's Django Project)

# Start this service after the network is available
After=network.target

[Service]
# The user and group that will run the Gunicorn process
# www-data is the default user that Nginx runs as
User=www-data
Group=www-data

# The root directory of the Django project
# This is the directory that contains manage.py
WorkingDirectory=/var/www/okabe-lab

# Start the app using the Gunicorn binary inside the virtual environment
# --workers: recommended value is (number of CPU cores × 2) + 1 (3 workers here)
# --bind: uses a Unix socket for communication with Nginx (faster than TCP)
# okabe_lab.wsgi:application: points to the application object in {Django project name}/wsgi.py
ExecStart=/var/www/okabe-lab/venv/bin/gunicorn \
          --workers 3 \
          --bind unix:/run/okabe-gunicorn.sock \
          okabe_lab.wsgi:application

# Automatically restart the process if it exits unexpectedly
Restart=on-failure

[Install]
# multi-user.target: start automatically in normal multi-user mode
WantedBy=multi-user.target
/etc/nginx/sites-available/okabe-django.conf
# -----------------------------------------------
#  /etc/nginx/sites-available/okabe-django.conf
# -----------------------------------------------
#  Reverse proxy configuration that forwards requests
#  from Nginx to Gunicorn

server {
    # Accept HTTP requests on port 80
    listen 80;

    # The domain name this server block handles
    server_name okabe-lab.example.com;

    # -----------------------------------------------
    #  Serve static files directly
    # -----------------------------------------------
    # Files collected by Django's collectstatic are served
    # directly by Nginx without going through Gunicorn (for performance)
    location /static/ {
        alias /var/www/okabe-lab/staticfiles/;
    }

    # Media files (user-uploaded images, etc.) are also served directly
    location /media/ {
        alias /var/www/okabe-lab/media/;
    }

    # -----------------------------------------------
    #  Reverse proxy to Gunicorn
    # -----------------------------------------------
    location / {
        # Forward requests to Gunicorn via Unix socket
        proxy_pass http://unix:/run/okabe-gunicorn.sock;

        # Pass the original hostname to the backend
        proxy_set_header Host $host;

        # Pass the client's real IP address to the backend
        proxy_set_header X-Real-IP $remote_addr;

        # Inform the backend that the request passed through a proxy
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # Pass the protocol (http/https) to the backend
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Run the following command:

$ sudo systemctl daemon-reload
$ sudo systemctl start okabe-gunicorn
$ sudo systemctl enable okabe-gunicorn
Created symlink /etc/systemd/system/multi-user.target.wants/okabe-gunicorn.service → /etc/systemd/system/okabe-gunicorn.service.
$ sudo systemctl status okabe-gunicorn
● okabe-gunicorn.service - Gunicorn daemon for okabe-lab (Rintaro Okabe's Django Project)
     Loaded: loaded (/etc/systemd/system/okabe-gunicorn.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2026-03-25 10:15:30 JST; 3s ago
   Main PID: 12345 (gunicorn)
      Tasks: 4 (limit: 4612)
     Memory: 72.4M
        CPU: 831ms
     CGroup: /system.slice/okabe-gunicorn.service
             ├─12345 /var/www/okabe-lab/venv/bin/python /var/www/okabe-lab/venv/bin/gunicorn --workers 3 --bind unix:/run/okabe-gunicorn.sock okabe_lab.wsgi:application
             ├─12346 /var/www/okabe-lab/venv/bin/python /var/www/okabe-lab/venv/bin/gunicorn --workers 3 --bind unix:/run/okabe-gunicorn.sock okabe_lab.wsgi:application
             ├─12347 /var/www/okabe-lab/venv/bin/python /var/www/okabe-lab/venv/bin/gunicorn --workers 3 --bind unix:/run/okabe-gunicorn.sock okabe_lab.wsgi:application
             └─12348 /var/www/okabe-lab/venv/bin/python /var/www/okabe-lab/venv/bin/gunicorn --workers 3 --bind unix:/run/okabe-gunicorn.sock okabe_lab.wsgi:application
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
$ curl -s -o /dev/null -w "%{http_code}" http://okabe-lab.example.com/
200

Overview

The standard production deployment for a Django app follows a four-layer architecture: virtualenv for environment isolation, Gunicorn for WSGI serving, systemd for process management, and Nginx as a reverse proxy. Using virtualenv lets you manage per-project dependencies without polluting the system Python environment. Gunicorn handles parallel requests with multiple worker processes, but its HTTP serving capabilities are limited — so it is standard practice to delegate static file delivery, SSL termination, and ports 80/443 to Nginx. Registering services with systemd is covered in detail on the systemd unit file page. For details on Nginx reverse proxy configuration, see the Nginx reverse proxy page. Using a Unix socket (unix:/run/okabe-gunicorn.sock) enables inter-process communication without consuming a TCP port, making it well-suited for Gunicorn-to-Nginx communication on the same server.

If you find any errors or copyright issues, please .