AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

AWX Behind Reverse Proxy: Nginx, Traefik, Caddy & Apache Setup Guide

By Luca Berton · Published 2024-01-01 · Category: installation

Complete guide to running AWX behind Nginx or Traefik reverse proxy. Configure SSL termination, custom domains, WebSocket proxying for live job output, health.

AWX runs on port 8052 by default with its own web server. Putting it behind a reverse proxy gives you SSL termination, custom domains, WebSocket support for live job output, and the ability to run alongside other services. Here's the complete setup for Nginx and Traefik.

Why Use a Reverse Proxy with AWX

SSL/TLS termination — HTTPS with Let's Encrypt certificates • Custom domainawx.example.com instead of server:8052WebSocket proxying — Required for live job output streaming • Load balancing — Multiple AWX instances behind one endpoint • Security — Hide AWX behind a hardened proxy, add rate limiting • Co-hosting — Run AWX alongside other services on the same server

See also: Automating Nginx Reverse Proxy Setup for Flask on RHEL

AWX with Nginx Reverse Proxy

Basic Nginx Configuration

# /etc/nginx/sites-available/awx.conf

upstream awx_backend { server 127.0.0.1:8052; }

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

server { listen 443 ssl http2; server_name awx.example.com;

# SSL certificates (Let's Encrypt) ssl_certificate /etc/letsencrypt/live/awx.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/awx.example.com/privkey.pem;

# SSL settings ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d;

# Security headers add_header Strict-Transport-Security "max-age=63072000" always; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff;

# Proxy settings 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;

# Increase timeouts for long-running API calls proxy_connect_timeout 120; proxy_send_timeout 120; proxy_read_timeout 120;

# Max upload size (for project imports) client_max_body_size 50M;

# Main application location / { proxy_pass http://awx_backend; }

# WebSocket support (critical for live job output) location /websocket/ { proxy_pass http://awx_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; }

# API endpoints location /api/ { proxy_pass http://awx_backend; proxy_buffering off; }

# Static files (optional — let AWX serve them or cache here) location /static/ { proxy_pass http://awx_backend; proxy_cache_valid 200 1d; add_header Cache-Control "public, max-age=86400"; } }

Deploy with Ansible

---
- name: Configure Nginx reverse proxy for AWX
  hosts: proxy_server
  become: true
  vars:
    awx_domain: awx.example.com
    awx_backend: 127.0.0.1:8052
    certbot_email: admin@example.com
  tasks:
    - name: Install Nginx and Certbot
      ansible.builtin.apt:
        name:
          - nginx
          - certbot
          - python3-certbot-nginx
        state: present
        update_cache: true

- name: Deploy Nginx configuration ansible.builtin.template: src: awx-nginx.conf.j2 dest: "/etc/nginx/sites-available/{{ awx_domain }}.conf" mode: '0644' notify: reload nginx

- name: Enable site ansible.builtin.file: src: "/etc/nginx/sites-available/{{ awx_domain }}.conf" dest: "/etc/nginx/sites-enabled/{{ awx_domain }}.conf" state: link notify: reload nginx

- name: Obtain SSL certificate ansible.builtin.command: cmd: > certbot --nginx -d {{ awx_domain }} --non-interactive --agree-tos --email {{ certbot_email }} creates: "/etc/letsencrypt/live/{{ awx_domain }}/fullchain.pem"

- name: Enable certbot auto-renewal ansible.builtin.cron: name: certbot renewal minute: "0" hour: "3" job: "certbot renew --quiet --post-hook 'systemctl reload nginx'"

handlers: - name: reload nginx ansible.builtin.service: name: nginx state: reloaded

AWX with Traefik Reverse Proxy

Docker Compose with Traefik

# docker-compose.yml
version: '3.8'

services: traefik: image: traefik:v3.0 command: - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - traefik-certs:/letsencrypt networks: - proxy

awx-web: image: quay.io/ansible/awx:latest labels: - "traefik.enable=true" - "traefik.http.routers.awx.rule=Host(`awx.example.com`)" - "traefik.http.routers.awx.entrypoints=websecure" - "traefik.http.routers.awx.tls.certresolver=letsencrypt" - "traefik.http.services.awx.loadbalancer.server.port=8052" # HTTP to HTTPS redirect - "traefik.http.routers.awx-http.rule=Host(`awx.example.com`)" - "traefik.http.routers.awx-http.entrypoints=web" - "traefik.http.routers.awx-http.middlewares=redirect-to-https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" # Security headers - "traefik.http.middlewares.awx-headers.headers.stsSeconds=63072000" - "traefik.http.middlewares.awx-headers.headers.frameDeny=true" - "traefik.http.middlewares.awx-headers.headers.contentTypeNosniff=true" - "traefik.http.routers.awx.middlewares=awx-headers" networks: - proxy depends_on: - awx-postgres - awx-redis

awx-postgres: image: postgres:15 environment: POSTGRES_DB: awx POSTGRES_USER: awx POSTGRES_PASSWORD: "${AWX_DB_PASSWORD}" volumes: - awx-postgres:/var/lib/postgresql/data networks: - proxy

awx-redis: image: redis:7 networks: - proxy

volumes: traefik-certs: awx-postgres:

networks: proxy: driver: bridge

Traefik File Configuration

# traefik/dynamic/awx.yml
http:
  routers:
    awx:
      rule: "Host(`awx.example.com`)"
      service: awx
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
      middlewares:
        - awx-headers
        - rate-limit

awx-ws: rule: "Host(`awx.example.com`) && PathPrefix(`/websocket`)" service: awx entryPoints: - websecure tls: certResolver: letsencrypt

services: awx: loadBalancer: servers: - url: "http://awx-web:8052" healthCheck: path: /api/v2/ping/ interval: 30s timeout: 5s

middlewares: awx-headers: headers: stsSeconds: 63072000 frameDeny: true contentTypeNosniff: true browserXssFilter: true

rate-limit: rateLimit: average: 100 burst: 50

See also: Ansible Nginx: Install, Configure & Automate (Complete Guide)

Kubernetes Ingress

Nginx Ingress Controller

# awx-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: awx-ingress
  namespace: awx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "120"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    # WebSocket support
    nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - awx.example.com
      secretName: awx-tls
  rules:
    - host: awx.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: awx-service
                port:
                  number: 80

Deploy Ingress with Ansible

- name: Configure AWX Kubernetes ingress
  kubernetes.core.k8s:
    state: present
    definition: "{{ lookup('file', 'awx-ingress.yml') }}"

AWX Configuration for Reverse Proxy

Set External URL

# Via AWX CLI
awx-manage shell -c "
from django.conf import settings
from awx.conf.models import Setting
Setting.objects.update_or_create(
    key='TOWER_URL_BASE',
    defaults={'value': '\"https://awx.example.com\"'}
)
"

Or in the AWX UI: Settings → System → Base URL of the servicehttps://awx.example.com

Trusted Proxies

# AWX settings
REMOTE_HOST_HEADERS:
  - HTTP_X_FORWARDED_FOR
  - REMOTE_ADDR
  - REMOTE_HOST

# Ensure AWX trusts the proxy headers PROXY_IP_ALLOWED_LIST: - 10.0.0.0/8 - 172.16.0.0/12 - 192.168.0.0/16

See also: Ansible London Meetup 2024 Recap: Highlights and Insights

WebSocket Configuration

WebSockets are critical for AWX — they stream live job output. Without proper WebSocket proxying, job output only updates on page refresh.

Test WebSocket Connection

# Install wscat
npm install -g wscat

# Test WebSocket wscat -c wss://awx.example.com/websocket/

# Should connect without errors # Type: {"xrftoken": "test"}

Common WebSocket Issues

# ❌ Missing Upgrade headers — WebSocket fails silently
location /websocket/ {
    proxy_pass http://awx_backend;
}

# ✅ Correct — with Upgrade and Connection headers location /websocket/ { proxy_pass http://awx_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; # Keep connection alive }

Health Check Endpoint

# Health check for load balancers
location /api/v2/ping/ {
    proxy_pass http://awx_backend;
    proxy_connect_timeout 5;
    proxy_read_timeout 5;
    access_log off;
}
# Ansible health check
- name: Verify AWX is accessible through proxy
  ansible.builtin.uri:
    url: https://awx.example.com/api/v2/ping/
    validate_certs: true
    status_code: 200
  register: awx_health
  retries: 10
  delay: 5
  until: awx_health.status == 200

Rate Limiting and Security

Nginx Rate Limiting

# Define rate limit zones
limit_req_zone $binary_remote_addr zone=awx_api:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=awx_login:10m rate=5r/m;

server { # Rate limit API location /api/ { limit_req zone=awx_api burst=50 nodelay; proxy_pass http://awx_backend; }

# Strict rate limit on login location /api/login/ { limit_req zone=awx_login burst=3 nodelay; proxy_pass http://awx_backend; } }

IP Allowlisting

# Restrict AWX to internal networks
location / {
    allow 10.0.0.0/8;
    allow 172.16.0.0/12;
    allow 192.168.0.0/16;
    deny all;
    proxy_pass http://awx_backend;
}

Caddy Configuration

Caddy automatically manages SSL certificates via Let's Encrypt — zero configuration needed for HTTPS:

# /etc/caddy/Caddyfile
awx.example.com {
    reverse_proxy 127.0.0.1:8080 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}

transport http { read_timeout 300s } }

request_body { max_size 50MB } }

Caddy handles WebSocket upgrades automatically — no extra configuration needed.

sudo systemctl reload caddy

Apache Configuration

# /etc/apache2/sites-available/awx.conf
<VirtualHost *:80>
    ServerName awx.example.com
    Redirect permanent / https://awx.example.com/
</VirtualHost>

<VirtualHost *:443> ServerName awx.example.com

SSLEngine on SSLCertificateFile /etc/letsencrypt/live/awx.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/awx.example.com/privkey.pem

ProxyPreserveHost On ProxyRequests Off

ProxyPass / http://127.0.0.1:8080/ ProxyPassReverse / http://127.0.0.1:8080/

# WebSocket support RewriteEngine On RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/websocket/(.*) ws://127.0.0.1:8080/websocket/$1 [P,L]

ProxyTimeout 300 RequestHeader set X-Forwarded-Proto "https" </VirtualHost>

Enable required Apache modules:

sudo a2enmod proxy proxy_http proxy_wstunnel ssl rewrite headers
sudo a2ensite awx.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

AWX Configuration for Reverse Proxy

Configure AWX to trust the proxy headers regardless of which reverse proxy you use:

extra_settings:
  - setting: CSRF_TRUSTED_ORIGINS
    value: '["https://awx.example.com"]'
  - setting: REMOTE_HOST_HEADERS
    value: '["HTTP_X_FORWARDED_FOR", "REMOTE_ADDR"]'
  - setting: USE_X_FORWARDED_HOST
    value: 'True'
  - setting: USE_X_FORWARDED_PORT
    value: 'True'

For AWX Operator on Kubernetes:

apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx
spec:
  extra_settings:
    - setting: CSRF_TRUSTED_ORIGINS
      value: '["https://awx.example.com"]'

FAQ

Why does AWX job output not update in real-time?

WebSocket connection is not working through your proxy. Ensure you have Upgrade and Connection headers set for the /websocket/ path, and proxy_read_timeout is high enough (86400 seconds) to keep the connection alive.

Do I need to change AWX's built-in SSL settings?

No. With a reverse proxy handling SSL termination, AWX communicates with the proxy over plain HTTP. The proxy handles all HTTPS. Set X-Forwarded-Proto: https so AWX knows the original request was secure.

How do I handle multiple AWX instances behind a load balancer?

Use sticky sessions (session affinity) — AWX uses session-based authentication that needs to hit the same backend. In Nginx, use ip_hash in the upstream block. In Traefik, use the sticky cookie middleware.

Can I run AWX on a subpath like /awx/?

AWX doesn't natively support subpath deployment. It expects to run at the root path. Use a subdomain (awx.example.com) instead of a subpath (example.com/awx/).

Conclusion

A reverse proxy in front of AWX gives you SSL, custom domains, and proper WebSocket support for live job output. The critical configuration is the WebSocket proxy for /websocket/ — without it, job output won't stream in real-time. Use Nginx for simplicity, Traefik for Docker/container environments, or Kubernetes Ingress for K8s deployments.

Related Articles

AWX Installation GuideAnsible for KubernetesAnsible Connection TypesAnsible Vault Deep Dive

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home