Initial Configuration Informational Other

SSL, Domain & Reverse Proxy

Back to manual
Configure Nginx as a reverse proxy for SecBoard: set up Gunicorn as a systemd service, obtain an SSL certificate with Certbot, deploy the HTTPS Nginx config, and enable production mode in credential.py.

Architecture Overview

In production, SecBoard runs as a Gunicorn WSGI process bound to 127.0.0.1 (loopback only). Nginx faces the internet, terminates TLS, serves static and media files directly, and proxies all other requests to Gunicorn.

Browser  ──HTTPS──►  Nginx :443  ──HTTP──►  Gunicorn 127.0.0.1:PORT
                         │
                         ├─► /static/  →  staticfiles/  (served directly)
                         └─► /media/   →  media/        (served directly)

Step 1 — Gunicorn systemd Service

SecBoard ships a ready-made service file: SecBoard_prod.service. Copy it to systemd and enable it:

sudo cp /srv/SecBoard/SecBoard_prod.service /etc/systemd/system/secboard.service
sudo systemctl daemon-reload
sudo systemctl enable secboard
sudo systemctl start secboard
sudo systemctl status secboard

Key parameters inside the service file:

ParameterValueNotes
--bind127.0.0.1:PORTLoopback only — never expose to 0.0.0.0
--workers3Rule of thumb: 2 × CPU cores + 1
--timeout120Seconds before a worker is killed for being unresponsive
--max-requests1000Workers restart after this many requests (prevents memory leaks)
--max-requests-jitter100Random spread to avoid all workers restarting simultaneously

Collect static files before starting:

source /srv/SecBoard/venv/bin/activate
python manage.py collectstatic --noinput

Step 2 — Nginx: HTTP-Only Config (before SSL)

Before obtaining the SSL certificate, use the HTTP-only config (nginx_secboard_http.conf) to verify that the domain resolves correctly and the app is reachable. Replace your-domain.com with your actual domain and PORT with the Gunicorn port from the service file.

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    client_max_body_size 100M;

    location /static/ {
        alias /srv/SecBoard/staticfiles/;
    }

    location /media/ {
        alias /srv/SecBoard/media/;
    }

    location / {
        proxy_pass http://127.0.0.1:PORT;
        proxy_set_header Host $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;
    }
}
sudo cp nginx_secboard_http.conf /etc/nginx/sites-available/secboard
sudo ln -s /etc/nginx/sites-available/secboard /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Open http://your-domain.com/ and confirm the login page loads.

Step 3 — SSL Certificate with Certbot

Install Certbot and obtain a certificate using the webroot method:

# Install certbot (Ubuntu/Debian)
sudo apt install certbot python3-certbot-nginx

# Obtain certificate (webroot method)
sudo certbot certonly --webroot     -w /srv/SecBoard/letsencrypt     -d your-domain.com     -d www.your-domain.com

After a successful issuance the certificate files are at:

  • /etc/letsencrypt/live/your-domain.com/fullchain.pem
  • /etc/letsencrypt/live/your-domain.com/privkey.pem

Certbot automatically installs a renewal cron / systemd timer. Verify it:

sudo certbot renew --dry-run

Step 4 — Nginx: Full HTTPS Config

Replace the HTTP-only config with the full HTTPS config (nginx_secboard.conf). Adjust server_name, certificate paths, and the proxy_pass port to match your installation:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com www.your-domain.com;

    ssl_certificate     /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    client_max_body_size 100M;

    location /static/ {
        alias /srv/SecBoard/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /srv/SecBoard/media/;
        expires 1y;
        add_header Cache-Control "public";
    }

    location / {
        proxy_pass http://127.0.0.1:PORT;
        proxy_set_header Host $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_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    location /health/ {
        proxy_pass http://127.0.0.1:PORT/health/;
        access_log off;
    }
}
sudo nginx -t && sudo systemctl reload nginx

Step 5 — Django Settings for Production

Edit SecBoard/credential.py to match your domain and enable production mode:

Switch to production mode
# credential.py
debug = False   # triggers PRODUCTION = True in settings.py → enables HSTS, secure cookies
ALLOWED_HOSTS
allowed_hosts = [
    '127.0.0.1',
    'your-domain.com',
    'www.your-domain.com',
]
CSRF_TRUSTED_ORIGINS
csrf_trusted_origins = [
    'https://your-domain.com',
    'https://www.your-domain.com',
]

Why this matters: when debug = False, Django enables SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https'), which lets it trust the X-Forwarded-Proto header that Nginx sets. Without it, Django would not know the request arrived over HTTPS.

Restart the service after any credential changes:

sudo systemctl restart secboard
sudo systemctl reload nginx
What debug = False enables automatically
SettingValue in production
SECURE_SSL_REDIRECTTrue — Django also redirects HTTP→HTTPS at app level
SESSION_COOKIE_SECURETrue — session cookie sent only over HTTPS
CSRF_COOKIE_SECURETrue — CSRF cookie sent only over HTTPS
SECURE_HSTS_SECONDS31536000 (1 year)
SECURE_HSTS_INCLUDE_SUBDOMAINSTrue
SECURE_HSTS_PRELOADTrue
SESSION_COOKIE_AGE2700 s (45 min) — shorter session timeout
SESSION_COOKIE_SAMESITEStrict

Setup Checklist

#Task
1Run collectstatic and start the Gunicorn systemd service
2Install Nginx and deploy the HTTP-only config; verify the app loads over HTTP
3Obtain an SSL certificate with Certbot (certonly --webroot)
4Replace the HTTP config with the full HTTPS config; reload Nginx
5Set debug = False in credential.py
6Update allowed_hosts and csrf_trusted_origins with your domain
7Restart the Gunicorn service
8Run certbot renew --dry-run to confirm auto-renewal works
9Check SSL Labs — aim for grade A or better

Next Steps

Roles & Permissions

Define roles and invite your team members with the appropriate access levels.

Updating SecBoard

Learn how to safely apply new releases, run database migrations, and update static files.


Attachments

No attachments for this article.