Install and configure Waitress WSGI server with SSL certificates for Python web applications

Beginner 25 min Apr 17, 2026 26 views
Ubuntu 24.04 Ubuntu 22.04 Debian 12 AlmaLinux 9 Rocky Linux 9 Fedora 41

Deploy production-ready Python web applications using Waitress WSGI server with SSL certificates, systemd service management, and reverse proxy configuration for Flask and Django applications.

Prerequisites

  • Root or sudo access
  • Python 3.8 or higher installed
  • Basic understanding of Python web applications

What this solves

Waitress is a production-ready Python WSGI server that handles HTTP requests for Flask and Django applications. Unlike development servers, Waitress provides thread-safe request handling, proper error logging, and SSL support needed for production deployments. This tutorial shows you how to deploy Python applications with Waitress behind a reverse proxy for optimal performance and security.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest package information and security updates.

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

Install Python and development tools

Install Python 3, pip, and essential development packages needed for Python application deployment.

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

Create application user and directory

Create a dedicated user and directory structure for your Python application with proper ownership and permissions.

sudo useradd --system --shell /bin/bash --home /opt/webapp webapp
sudo mkdir -p /opt/webapp/app /opt/webapp/logs /opt/webapp/ssl
sudo chown -R webapp:webapp /opt/webapp

Create sample Flask application

Create a simple Flask application to demonstrate Waitress deployment. This example includes basic logging and error handling.

#!/usr/bin/env python3
from flask import Flask, jsonify, request
import logging
import os
from datetime import datetime

app = Flask(__name__)

Configure logging

logging.basicConfig( filename='/opt/webapp/logs/app.log', level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s' ) @app.route('/') def home(): app.logger.info(f'Home page accessed from {request.remote_addr}') return jsonify({ 'message': 'Hello from Waitress WSGI Server', 'timestamp': datetime.now().isoformat(), 'server': 'waitress' }) @app.route('/health') def health(): return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()}) @app.route('/api/data') def api_data(): app.logger.info('API data endpoint accessed') return jsonify({ 'data': ['item1', 'item2', 'item3'], 'count': 3, 'timestamp': datetime.now().isoformat() }) if __name__ == '__main__': app.run(debug=False)

Create Python virtual environment

Set up an isolated Python environment for your application dependencies to avoid conflicts with system packages.

sudo -u webapp python3 -m venv /opt/webapp/venv
sudo -u webapp /opt/webapp/venv/bin/pip install --upgrade pip
sudo -u webapp /opt/webapp/venv/bin/pip install flask waitress

Create Waitress application runner

Create a Python script that configures Waitress with production settings including threading, timeout, and logging options.

#!/usr/bin/env python3
from waitress import serve
from app import app
import logging
import os

Configure Waitress logging

logging.basicConfig( filename='/opt/webapp/logs/waitress.log', level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s' ) if __name__ == '__main__': # Waitress production configuration serve( app, host='127.0.0.1', port=8080, threads=6, connection_limit=100, cleanup_interval=30, channel_timeout=120, log_socket_errors=True, call=True )

Generate SSL certificates

Create self-signed SSL certificates for development or testing. For production, replace these with certificates from a trusted certificate authority.

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout /opt/webapp/ssl/webapp.key \
    -out /opt/webapp/ssl/webapp.crt \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=example.com"
    
sudo chown webapp:webapp /opt/webapp/ssl/webapp.*
sudo chmod 600 /opt/webapp/ssl/webapp.key
sudo chmod 644 /opt/webapp/ssl/webapp.crt
Production note: Use certificates from Let's Encrypt or a commercial CA for production deployments. Self-signed certificates should only be used for development and testing.

Configure systemd service

Create a systemd service file to manage the Waitress application with automatic startup, restart policies, and proper logging.

[Unit]
Description=Waitress WSGI Server for Web Application
After=network.target
Wants=network.target

[Service]
Type=exec
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp/app
Environment=PATH=/opt/webapp/venv/bin
ExecStart=/opt/webapp/venv/bin/python wsgi.py
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=10

Security settings

NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/opt/webapp/logs

Logging

StandardOutput=append:/opt/webapp/logs/webapp-stdout.log StandardError=append:/opt/webapp/logs/webapp-stderr.log [Install] WantedBy=multi-user.target

Set up log rotation

Configure logrotate to manage application logs and prevent disk space issues from growing log files.

/opt/webapp/logs/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    copytruncate
    su webapp webapp
    postrotate
        systemctl reload webapp 2>/dev/null || true
    endscript
}

Configure NGINX reverse proxy

Set up NGINX as a reverse proxy to handle SSL termination, static files, and load balancing for the Waitress application.

upstream webapp_backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    
    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;
    
    # SSL Configuration
    ssl_certificate /opt/webapp/ssl/webapp.crt;
    ssl_certificate_key /opt/webapp/ssl/webapp.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Security headers
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Logging
    access_log /var/log/nginx/webapp-access.log;
    error_log /var/log/nginx/webapp-error.log warn;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    
    location / {
        proxy_pass http://webapp_backend;
        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;
        
        # Timeouts
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
        
        # Keep connections alive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        
        # Buffer settings
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }
    
    # Health check endpoint
    location /health {
        proxy_pass http://webapp_backend/health;
        access_log off;
    }
    
    # Static files (if needed)
    location /static {
        alias /opt/webapp/app/static;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Enable and start services

Enable the NGINX site configuration and start all required services with proper startup ordering.

sudo ln -s /etc/nginx/sites-available/webapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

sudo systemctl daemon-reload
sudo systemctl enable webapp
sudo systemctl start webapp

Configure firewall rules

Open the necessary ports for HTTP and HTTPS traffic while maintaining security.

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload

Create Django application example

Install Django and create project

For Django applications, install Django and create a sample project structure that works with Waitress.

sudo -u webapp /opt/webapp/venv/bin/pip install django
sudo -u webapp mkdir -p /opt/webapp/django-app
cd /opt/webapp/django-app
sudo -u webapp /opt/webapp/venv/bin/django-admin startproject myproject .

Configure Django settings

Update Django settings for production use with Waitress, including security settings and database configuration.

# Add these settings to your Django settings.py

DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com', '127.0.0.1']

Security settings

SECRET_KEY = 'your-secret-key-here-change-in-production' SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True

Logging

LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.FileHandler', 'filename': '/opt/webapp/logs/django.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'INFO', 'propagate': True, }, }, }

Create Django WSGI runner

Create a Waitress configuration specifically for Django applications with optimized settings.

#!/usr/bin/env python3
import os
import sys
from waitress import serve
import django
from django.core.wsgi import get_wsgi_application

Add the project directory to Python path

sys.path.append('/opt/webapp/django-app')

Set Django settings module

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

Initialize Django

django.setup()

Get WSGI application

application = get_wsgi_application() if __name__ == '__main__': serve( application, host='127.0.0.1', port=8080, threads=8, connection_limit=200, cleanup_interval=30, channel_timeout=120, log_socket_errors=True )

Configure SSL with Waitress directly

Install PyOpenSSL for SSL support

Install additional packages needed for SSL support directly in Waitress without a reverse proxy.

sudo -u webapp /opt/webapp/venv/bin/pip install pyopenssl

Create SSL-enabled Waitress runner

Configure Waitress to handle SSL directly, useful for simpler deployments without a reverse proxy.

#!/usr/bin/env python3
from waitress import serve
from app import app
import logging

logging.basicConfig(
    filename='/opt/webapp/logs/waitress-ssl.log',
    level=logging.INFO,
    format='%(asctime)s %(levelname)s: %(message)s'
)

if __name__ == '__main__':
    serve(
        app,
        host='0.0.0.0',
        port=8443,
        threads=6,
        connection_limit=100,
        cleanup_interval=30,
        channel_timeout=120,
        # SSL configuration
        ssl_cert='/opt/webapp/ssl/webapp.crt',
        ssl_key='/opt/webapp/ssl/webapp.key',
        # Optional: require SSL client certificates
        # ssl_ca_cert='/path/to/ca-cert.pem',
        log_socket_errors=True
    )

Verify your setup

# Check service status
sudo systemctl status webapp
sudo systemctl status nginx

Test application endpoints

curl -k https://example.com/ curl -k https://example.com/health curl -k https://example.com/api/data

Check logs

sudo tail -f /opt/webapp/logs/app.log sudo tail -f /opt/webapp/logs/waitress.log sudo tail -f /var/log/nginx/webapp-access.log

Verify SSL certificate

openssl s_client -connect example.com:443 -servername example.com

Test performance

ab -n 100 -c 10 https://example.com/

Performance optimization

Tune Waitress for high traffic

Optimize Waitress configuration for production workloads with higher connection limits and threading.

#!/usr/bin/env python3
from waitress import serve
from app import app
import logging
import multiprocessing

Calculate optimal thread count

thread_count = multiprocessing.cpu_count() * 2 logging.basicConfig( filename='/opt/webapp/logs/waitress.log', level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s' ) if __name__ == '__main__': serve( app, host='127.0.0.1', port=8080, threads=thread_count, connection_limit=1000, cleanup_interval=30, channel_timeout=120, recv_bytes=65536, send_bytes=18000, max_request_header_size=262144, max_request_body_size=1073741824, expose_tracebacks=False, log_socket_errors=True )

Configure application monitoring

Set up basic monitoring and health checks for your Waitress application to track performance and availability.

#!/usr/bin/env python3
from flask import Flask, jsonify
import psutil
import time
from datetime import datetime

def add_monitoring_routes(app):
    @app.route('/metrics')
    def metrics():
        return jsonify({
            'timestamp': datetime.now().isoformat(),
            'uptime': time.time() - psutil.boot_time(),
            'cpu_percent': psutil.cpu_percent(interval=1),
            'memory_percent': psutil.virtual_memory().percent,
            'disk_usage': psutil.disk_usage('/').percent,
            'process_count': len(psutil.pids())
        })
    
    @app.route('/status')
    def status():
        return jsonify({
            'status': 'running',
            'version': '1.0.0',
            'server': 'waitress',
            'timestamp': datetime.now().isoformat()
        })

Add to your main app.py

from monitoring import add_monitoring_routes

add_monitoring_routes(app)

Common issues

Symptom Cause Fix
Service fails to start Python virtual environment path issues Check Environment=PATH in systemd service and verify venv exists
502 Bad Gateway from NGINX Waitress not listening on expected port Verify Waitress is running: sudo netstat -tlnp | grep 8080
Permission denied errors Incorrect file ownership or permissions Fix ownership: sudo chown -R webapp:webapp /opt/webapp
SSL certificate errors Invalid certificate paths or permissions Verify paths in NGINX config and certificate permissions: chmod 600 private.key
High memory usage Too many threads or memory leaks Reduce thread count in Waitress config and monitor with htop
Slow response times Database connections or blocking operations Add connection pooling and use async operations where possible

Next steps

Running this in production?

This works for a single server. When you run multiple environments or need this available 24/7, keeping it healthy is a different job. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

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