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
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
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
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
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
- Monitor Python applications with Prometheus and Grafana
- Set up NGINX load balancing for multiple Waitress instances
- Configure Waitress with Docker containers and docker-compose
- Implement blue-green deployment strategies with Waitress
- Configure Linux system services with systemctl and service management
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
DOMAIN="${1:-webapp.local}"
APP_USER="webapp"
APP_HOME="/opt/webapp"
# Usage message
usage() {
echo "Usage: $0 [domain_name]"
echo "Example: $0 example.com"
exit 1
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop waitress-webapp 2>/dev/null || true
systemctl disable waitress-webapp 2>/dev/null || true
rm -f /etc/systemd/system/waitress-webapp.service
userdel -r $APP_USER 2>/dev/null || true
rm -rf $APP_HOME
systemctl reload nginx 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
echo -e "${GREEN}Waitress WSGI Server Installation Script${NC}"
echo "Domain: $DOMAIN"
# Detect distribution
echo -e "${YELLOW}[1/12] Detecting distribution...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
FIREWALL_CMD="ufw"
PYTHON_DEV="python3-dev"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
FIREWALL_CMD="firewall-cmd"
PYTHON_DEV="python3-devel"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
FIREWALL_CMD="firewall-cmd"
PYTHON_DEV="python3-devel"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
# Update system packages
echo -e "${YELLOW}[2/12] Updating system packages...${NC}"
$PKG_UPDATE
# Install Python and development tools
echo -e "${YELLOW}[3/12] Installing Python and dependencies...${NC}"
$PKG_INSTALL python3 python3-pip python3-venv $PYTHON_DEV build-essential nginx openssl
# Enable and start nginx
systemctl enable nginx
systemctl start nginx
# Create application user and directories
echo -e "${YELLOW}[4/12] Creating application user and directories...${NC}"
useradd --system --shell /bin/bash --home $APP_HOME $APP_USER || true
mkdir -p $APP_HOME/{app,logs,ssl}
chown -R $APP_USER:$APP_USER $APP_HOME
chmod 750 $APP_HOME
chmod 755 $APP_HOME/app
chmod 755 $APP_HOME/logs
chmod 700 $APP_HOME/ssl
# Create Flask application
echo -e "${YELLOW}[5/12] Creating Flask application...${NC}"
cat > $APP_HOME/app/app.py << 'EOF'
#!/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)
EOF
# Create virtual environment and install packages
echo -e "${YELLOW}[6/12] Creating Python virtual environment...${NC}"
sudo -u $APP_USER python3 -m venv $APP_HOME/venv
sudo -u $APP_USER $APP_HOME/venv/bin/pip install --upgrade pip
sudo -u $APP_USER $APP_HOME/venv/bin/pip install flask waitress
# Create Waitress runner script
echo -e "${YELLOW}[7/12] Creating Waitress runner...${NC}"
cat > $APP_HOME/app/run_waitress.py << 'EOF'
#!/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__':
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
)
EOF
chmod 644 $APP_HOME/app/*.py
chown -R $APP_USER:$APP_USER $APP_HOME/app
# Generate SSL certificates
echo -e "${YELLOW}[8/12] Generating SSL certificates...${NC}"
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout $APP_HOME/ssl/webapp.key \
-out $APP_HOME/ssl/webapp.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=$DOMAIN"
chown $APP_USER:$APP_USER $APP_HOME/ssl/webapp.*
chmod 600 $APP_HOME/ssl/webapp.*
# Create systemd service
echo -e "${YELLOW}[9/12] Creating systemd service...${NC}"
cat > /etc/systemd/system/waitress-webapp.service << EOF
[Unit]
Description=Waitress WSGI Server for Web Application
After=network.target
[Service]
Type=exec
User=$APP_USER
Group=$APP_USER
WorkingDirectory=$APP_HOME/app
Environment=PATH=$APP_HOME/venv/bin
ExecStart=$APP_HOME/venv/bin/python run_waitress.py
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable waitress-webapp
systemctl start waitress-webapp
# Configure Nginx
echo -e "${YELLOW}[10/12] Configuring Nginx...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
NGINX_CONF_FILE="$NGINX_CONF_DIR/webapp"
cat > $NGINX_CONF_FILE << EOF
server {
listen 80;
server_name $DOMAIN;
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name $DOMAIN;
ssl_certificate $APP_HOME/ssl/webapp.crt;
ssl_certificate_key $APP_HOME/ssl/webapp.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
EOF
ln -sf $NGINX_CONF_FILE $NGINX_ENABLED_DIR/
else
cat > $NGINX_CONF_DIR/webapp.conf << EOF
server {
listen 80;
server_name $DOMAIN;
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name $DOMAIN;
ssl_certificate $APP_HOME/ssl/webapp.crt;
ssl_certificate_key $APP_HOME/ssl/webapp.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
EOF
fi
nginx -t && systemctl reload nginx
# Configure firewall
echo -e "${YELLOW}[11/12] Configuring firewall...${NC}"
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw --force enable
ufw allow 'Nginx Full'
elif [[ "$FIREWALL_CMD" == "firewall-cmd" ]]; then
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
# Configure SELinux for RHEL-based systems
if [[ "$PKG_MGR" != "apt" ]]; then
if command -v setsebool &> /dev/null; then
setsebool -P httpd_can_network_connect 1
fi
fi
# Final verification
echo -e "${YELLOW}[12/12] Verifying installation...${NC}"
sleep 5
# Check service status
if systemctl is-active --quiet waitress-webapp; then
echo -e "${GREEN}✓ Waitress service is running${NC}"
else
echo -e "${RED}✗ Waitress service failed to start${NC}"
systemctl status waitress-webapp
exit 1
fi
if systemctl is-active --quiet nginx; then
echo -e "${GREEN}✓ Nginx is running${NC}"
else
echo -e "${RED}✗ Nginx failed to start${NC}"
exit 1
fi
# Test application response
if curl -sf http://127.0.0.1:8080/health > /dev/null; then
echo -e "${GREEN}✓ Application is responding${NC}"
else
echo -e "${RED}✗ Application is not responding${NC}"
exit 1
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "Application is available at: https://$DOMAIN"
echo -e "Health check: https://$DOMAIN/health"
echo -e "API endpoint: https://$DOMAIN/api/data"
echo -e "${YELLOW}Note: Using self-signed certificates. Replace with trusted certificates for production.${NC}"
Review the script before running. Execute with: bash install.sh