Configure Apache Airflow with SSL certificates and NGINX reverse proxy for secure production deployment

Intermediate 35 min Apr 21, 2026 13 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up Apache Airflow behind NGINX with SSL certificates, security headers, and reverse proxy configuration for production-grade deployments with HTTPS termination.

Prerequisites

  • Apache Airflow already installed
  • Domain name pointing to your server
  • Root or sudo access

What this solves

Apache Airflow's built-in webserver works for development, but production deployments need proper SSL termination, security headers, and load balancing. This tutorial configures NGINX as a reverse proxy with Let's Encrypt certificates to secure your Airflow web interface and API endpoints.

Step-by-step configuration

Install NGINX and Certbot

Install NGINX web server and Certbot for Let's Encrypt certificate management.

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo dnf install -y nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Configure Apache Airflow for reverse proxy

Modify Airflow configuration to work properly behind a reverse proxy with SSL termination.

# Webserver Configuration
[webserver]
web_server_host = 127.0.0.1
web_server_port = 8080
base_url = https://airflow.example.com
enable_proxy_fix = True

Security

secret_key = your-generated-secret-key-here

Logging

log_level = INFO

Generate SSL certificates with Let's Encrypt

Request SSL certificates for your Airflow domain using Certbot.

Prerequisites: Your domain must point to your server's public IP address before running this command.
sudo certbot certonly --nginx -d airflow.example.com
sudo systemctl reload nginx

Create NGINX reverse proxy configuration

Configure NGINX to proxy requests to Airflow with SSL termination and security headers.

upstream airflow_webserver {
    server 127.0.0.1:8080 fail_timeout=0;
}

server {
    listen 80;
    server_name airflow.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name airflow.example.com;
    
    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/airflow.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/airflow.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Security Headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'";
    
    # Client settings
    client_max_body_size 100M;
    
    # Proxy settings
    location / {
        proxy_pass http://airflow_webserver;
        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;
        
        # WebSocket support for Airflow
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
    
    # Health check endpoint
    location /health {
        proxy_pass http://airflow_webserver/health;
        proxy_set_header Host $http_host;
        access_log off;
    }
    
    # Static files caching
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
        proxy_pass http://airflow_webserver;
        proxy_set_header Host $http_host;
        expires 1M;
        add_header Cache-Control "public, immutable";
    }
}

Enable the NGINX configuration

Activate the Airflow site configuration and test the NGINX configuration.

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

Configure SSL hardening

Add additional SSL security configurations for production hardening.

# SSL Security Configuration
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/airflow.example.com/chain.pem;

Diffie-Hellman parameters

ssl_dhparam /etc/ssl/certs/dhparam.pem;

OCSP settings

resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s;
sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
sudo systemctl reload nginx

Configure Airflow systemd service

Set up Airflow to run as a system service with proper security settings.

[Unit]
Description=Airflow webserver daemon
After=network.target postgresql.service mysql.service redis.service
Wants=postgresql.service mysql.service redis.service

[Service]
EnvironmentFile=/opt/airflow/.env
User=airflow
Group=airflow
Type=notify
PIDFile=/opt/airflow/airflow-webserver.pid
ExecStart=/opt/airflow/venv/bin/airflow webserver --pid /opt/airflow/airflow-webserver.pid
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Create Airflow user and set permissions

Create a dedicated user for running Airflow with proper file permissions.

sudo useradd --system --shell /bin/bash --home-dir /opt/airflow --create-home airflow
sudo chown -R airflow:airflow /opt/airflow
sudo chmod 750 /opt/airflow
sudo chmod 640 /opt/airflow/airflow.cfg
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.

Set up automatic certificate renewal

Configure automatic SSL certificate renewal with systemd timer.

sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
sudo systemctl status certbot.timer
#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh

Configure rate limiting

Add rate limiting to protect against abuse and DoS attacks.

# Rate limiting zones
limit_req_zone $binary_remote_addr zone=airflow_api:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=airflow_web:10m rate=100r/m;

Connection limiting

limit_conn_zone $binary_remote_addr zone=airflow_conn:10m;

Update the main server block to include rate limiting.

sudo sed -i '/location \/ {/a\        limit_req zone=airflow_web burst=20 nodelay;\n        limit_conn airflow_conn 10;' /etc/nginx/sites-available/airflow
sudo nginx -t && sudo systemctl reload nginx

Start Airflow services

Enable and start the Airflow webserver service.

sudo systemctl enable --now airflow-webserver
sudo systemctl status airflow-webserver

Verify your setup

Test your Airflow installation with SSL and reverse proxy configuration.

# Check NGINX status and configuration
sudo systemctl status nginx
sudo nginx -t

Check Airflow webserver

sudo systemctl status airflow-webserver

Test SSL certificate

echo | openssl s_client -servername airflow.example.com -connect airflow.example.com:443 2>/dev/null | openssl x509 -noout -dates

Test HTTPS connection

curl -I https://airflow.example.com/health

Check security headers

curl -s -D- https://airflow.example.com/ | grep -E "(Strict-Transport-Security|X-Content-Type-Options|X-Frame-Options)"

Verify rate limiting

for i in {1..15}; do curl -s -o /dev/null -w "%{http_code}\n" https://airflow.example.com/; done

Common issues

SymptomCauseFix
502 Bad GatewayAirflow webserver not runningsudo systemctl status airflow-webserver and check logs
SSL certificate errorsDomain DNS not pointing to serverVerify DNS with dig airflow.example.com
Permission denied on filesIncorrect file ownershipsudo chown -R airflow:airflow /opt/airflow
Rate limiting too aggressiveLow rate limit settingsAdjust values in /etc/nginx/conf.d/rate-limit.conf
WebSocket connections failMissing proxy headersVerify proxy_set_header Upgrade in NGINX config
Static files not loadingProxy cache headers conflictCheck static file location block in NGINX

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. 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 devops services for businesses that depend on uptime. From initial setup to ongoing operations.