Install and configure Gunicorn application server with systemd and reverse proxy for Python web applications

Intermediate 25 min Apr 03, 2026 22 views
Ubuntu 24.04 Ubuntu 22.04 Debian 12 AlmaLinux 9 Rocky Linux 9 Fedora 41

Deploy Python web applications in production using Gunicorn WSGI server with systemd service management and Nginx reverse proxy. Includes performance tuning, security hardening, and monitoring setup.

Prerequisites

  • Root or sudo access
  • Basic understanding of Python web applications
  • Domain name pointing to your server

What this solves

Gunicorn (Green Unicorn) is a Python WSGI HTTP server that serves Python web applications like Django and Flask in production environments. This tutorial shows you how to install Gunicorn, configure it with optimal worker settings, set up automatic service management with systemd, and secure it behind an Nginx reverse proxy.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of all dependencies.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Python and development tools

Install Python 3, pip, and virtual environment tools needed for application deployment.

sudo apt install -y python3 python3-pip python3-venv python3-dev build-essential
sudo dnf install -y python3 python3-pip python3-venv python3-devel gcc

Create application user

Create a dedicated system user for running your Python application securely without shell access.

sudo useradd --system --group --no-create-home --shell /bin/false myapp
sudo mkdir -p /var/www/myapp
sudo chown myapp:myapp /var/www/myapp

Create Python virtual environment

Set up an isolated Python environment for your application dependencies.

cd /var/www/myapp
sudo -u myapp python3 -m venv venv
sudo -u myapp /var/www/myapp/venv/bin/pip install --upgrade pip

Install Gunicorn and your application

Install Gunicorn in the virtual environment along with your web framework.

sudo -u myapp /var/www/myapp/venv/bin/pip install gunicorn

For Django applications:

sudo -u myapp /var/www/myapp/venv/bin/pip install django

For Flask applications:

sudo -u myapp /var/www/myapp/venv/bin/pip install flask

Create sample application

Create a basic WSGI application for testing. Replace this with your actual application code.

#!/usr/bin/env python3

def application(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/html; charset=utf-8')]
    start_response(status, headers)
    return [b'

Hello from Gunicorn!

'] if __name__ == '__main__': from wsgiref.simple_server import make_server server = make_server('127.0.0.1', 8000, application) server.serve_forever()
sudo chown myapp:myapp /var/www/myapp/wsgi.py
sudo chmod 644 /var/www/myapp/wsgi.py

Configure Gunicorn settings

Create a Gunicorn configuration file with optimized worker settings for production use.

# Gunicorn configuration file

Server socket

bind = "127.0.0.1:8000" backlog = 2048

Worker processes

workers = 3 worker_class = "sync" worker_connections = 1000 timeout = 30 keepalive = 2 max_requests = 1000 max_requests_jitter = 50

Security

limit_request_line = 4094 limit_request_fields = 100 limit_request_field_size = 8190

Logging

accesslog = "/var/log/myapp/access.log" errorlog = "/var/log/myapp/error.log" loglevel = "info" access_log_format = '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"'

Process naming

proc_name = 'myapp'

Server mechanics

daemon = False pidfile = '/var/run/myapp/gunicorn.pid' user = 'myapp' group = 'myapp' tmp_upload_dir = None

SSL (uncomment if using HTTPS directly)

keyfile = '/path/to/private.key'

certfile = '/path/to/certificate.crt'

sudo chown myapp:myapp /var/www/myapp/gunicorn.conf.py
sudo chmod 644 /var/www/myapp/gunicorn.conf.py

Create log directories

Set up logging directories with proper permissions for the application user.

sudo mkdir -p /var/log/myapp /var/run/myapp
sudo chown myapp:myapp /var/log/myapp /var/run/myapp
sudo chmod 755 /var/log/myapp /var/run/myapp
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 644 for files and 755 for directories.

Create systemd service file

Configure systemd to manage Gunicorn as a service with automatic restart and proper resource limits.

[Unit]
Description=Gunicorn instance to serve myapp
After=network.target

[Service]
Type=notify
User=myapp
Group=myapp
RuntimeDirectory=myapp
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/gunicorn --config gunicorn.conf.py wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=5
StartLimitInterval=60
StartLimitBurst=3
KillMode=mixed
TimeoutStopSec=5

Security settings

NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ReadWritePaths=/var/log/myapp /var/run/myapp ProtectHome=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes

Resource limits

LimitNOFILE=65536 LimitNPROC=4096 [Install] WantedBy=multi-user.target

Enable and start Gunicorn service

Enable the service to start automatically on boot and start it immediately.

sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp

Install and configure Nginx

Set up Nginx as a reverse proxy to handle static files and SSL termination.

sudo apt install -y nginx
sudo dnf install -y nginx

Configure Nginx virtual host

Create an Nginx configuration with security headers and proper proxy settings.

upstream myapp_backend {
    server 127.0.0.1:8000 fail_timeout=0;
}

server {
    listen 80;
    server_name example.com www.example.com;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
    
    # Hide server information
    server_tokens off;
    
    # Client settings
    client_max_body_size 10M;
    client_body_timeout 60s;
    client_header_timeout 60s;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
    
    # Static files
    location /static/ {
        alias /var/www/myapp/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    location /media/ {
        alias /var/www/myapp/media/;
        expires 1y;
        add_header Cache-Control "public";
    }
    
    # Favicon
    location = /favicon.ico {
        alias /var/www/myapp/static/favicon.ico;
        log_not_found off;
        access_log off;
    }
    
    # Application proxy
    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_buffering on;
        proxy_pass http://myapp_backend;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    
    # Health check endpoint
    location /health/ {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

Enable Nginx site and test configuration

Activate the site configuration and verify Nginx syntax before restarting.

# On Ubuntu/Debian systems
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default

Test configuration

sudo nginx -t

Restart services

sudo systemctl restart nginx sudo systemctl enable nginx

Configure firewall

Open the necessary ports for HTTP and HTTPS traffic while keeping the system secure.

sudo ufw allow 'Nginx Full'
sudo ufw --force enable
sudo ufw status
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services

Set up log rotation

Configure logrotate to manage application logs and prevent disk space issues.

/var/log/myapp/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    copytruncate
    create 644 myapp myapp
    postrotate
        /bin/kill -USR1 $(cat /var/run/myapp/gunicorn.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

Performance tuning

Calculate optimal worker count

Determine the ideal number of Gunicorn workers based on your server's CPU cores.

# Check CPU cores
nproc

Formula: (2 x CPU cores) + 1

For 4 cores: workers = 9

Edit /var/www/myapp/gunicorn.conf.py and update workers value

sudo systemctl restart myapp

Configure system limits

Increase system limits for file descriptors and processes to handle more concurrent connections.

myapp soft nofile 65536
myapp hard nofile 65536
myapp soft nproc 4096
myapp hard nproc 4096

Optimize Nginx worker processes

Configure Nginx to use optimal worker settings for your hardware.

# Add or modify these settings in the main context
worker_processes auto;
worker_connections 1024;
worker_rlimit_nofile 65536;

In http context

keepalive_timeout 65; keepalive_requests 1000;
sudo nginx -t
sudo systemctl restart nginx

Verify your setup

Test that all components are working correctly and communicating properly.

# Check service status
sudo systemctl status myapp nginx

Test Gunicorn directly

curl -I http://127.0.0.1:8000/

Test through Nginx

curl -I http://localhost/

Check logs

sudo tail -f /var/log/myapp/error.log sudo tail -f /var/log/nginx/error.log

Monitor worker processes

ps aux | grep gunicorn

Security hardening

Configure SSL certificates

Set up SSL/TLS certificates for secure HTTPS connections. You can get free certificates from Let's Encrypt.

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo dnf install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Set up monitoring with systemd

Configure systemd to restart failed services automatically and log service events.

# Enable detailed logging
sudo systemctl edit myapp
[Service]
Environment="PYTHONPATH=/var/www/myapp"
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
sudo systemctl daemon-reload
sudo systemctl restart myapp
Note: For comprehensive system monitoring and alerting, consider setting up Prometheus and Grafana monitoring or systemd resource monitoring.

Common issues

SymptomCauseFix
Service fails to startPermission denied on log directorysudo chown -R myapp:myapp /var/log/myapp
502 Bad Gateway errorGunicorn not listening on correct portCheck bind setting in gunicorn.conf.py matches Nginx upstream
Workers dying frequentlyMemory exhaustion or timeout issuesIncrease worker memory limits or reduce max_requests
Static files not loadingIncorrect Nginx static file pathsVerify alias paths in Nginx config match actual file locations
High CPU usageToo many workers for available coresReduce worker count to (2 x cores) + 1
Connection refusedFirewall blocking accessCheck firewall rules with sudo ufw status or sudo firewall-cmd --list-services

Next steps

Automated install script

Run this to automate the entire setup

#gunicorn #wsgi-server #python-deployment #systemd-service #nginx-gunicorn

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer