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)
127.0.0.1, not 0.0.0.0. All public traffic
must go through Nginx.
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:
| Parameter | Value | Notes |
|---|---|---|
--bind | 127.0.0.1:PORT | Loopback only — never expose to 0.0.0.0 |
--workers | 3 | Rule of thumb: 2 × CPU cores + 1 |
--timeout | 120 | Seconds before a worker is killed for being unresponsive |
--max-requests | 1000 | Workers restart after this many requests (prevents memory leaks) |
--max-requests-jitter | 100 | Random 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
/srv/SecBoard/letsencrypt). The HTTP Nginx config must serve
/.well-known/acme-challenge/ from that directory for validation to succeed.
Create the directory first: mkdir -p /srv/SecBoard/letsencrypt.
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
debug = True in production.
Debug mode exposes full tracebacks and disables security settings such as
SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, and HSTS.
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
| Setting | Value in production |
|---|---|
SECURE_SSL_REDIRECT | True — Django also redirects HTTP→HTTPS at app level |
SESSION_COOKIE_SECURE | True — session cookie sent only over HTTPS |
CSRF_COOKIE_SECURE | True — CSRF cookie sent only over HTTPS |
SECURE_HSTS_SECONDS | 31536000 (1 year) |
SECURE_HSTS_INCLUDE_SUBDOMAINS | True |
SECURE_HSTS_PRELOAD | True |
SESSION_COOKIE_AGE | 2700 s (45 min) — shorter session timeout |
SESSION_COOKIE_SAMESITE | Strict |
Setup Checklist
| # | Task |
|---|---|
| 1 | Run collectstatic and start the Gunicorn systemd service |
| 2 | Install Nginx and deploy the HTTP-only config; verify the app loads over HTTP |
| 3 | Obtain an SSL certificate with Certbot (certonly --webroot) |
| 4 | Replace the HTTP config with the full HTTPS config; reload Nginx |
| 5 | Set debug = False in credential.py |
| 6 | Update allowed_hosts and csrf_trusted_origins with your domain |
| 7 | Restart the Gunicorn service |
| 8 | Run certbot renew --dry-run to confirm auto-renewal works |
| 9 | Check 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.