Home DevOps Ansible AWX & Tower: Production Automation Without the Fire Drills
Advanced 3 min · July 11, 2026

Ansible AWX & Tower: Production Automation Without the Fire Drills

Ansible AWX and Tower guide for production: avoid job failures, manage secrets, scale workers, and debug like a senior engineer..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 11, 2026
last updated
1,727
articles · all by Naren
Before you start⏱ 35 min
  • A Kubernetes cluster (K3s, EKS, AKS, GKE). kubectl and Helm installed. At least 4GB RAM available for testing.
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

AWX/Tower centralizes Ansible execution, adds RBAC, job scheduling, and a REST API. Use it when you need to manage multiple teams, schedule recurring jobs, or audit playbook runs. Don't use it for a handful of ad-hoc playbooks — plain Ansible is simpler.

✦ Definition~90s read
What is Ansible AWX and Tower?

AWX is the open-source upstream of Red Hat Ansible Automation Platform (formerly Tower). It provides a web UI, REST API, and job scheduling for Ansible playbooks. Tower is the enterprise version with support and additional features like RBAC and smart inventories.

Imagine you're a chef with a recipe book (Ansible playbooks).
Plain-English First

Imagine you're a chef with a recipe book (Ansible playbooks). AWX/Tower is the kitchen manager: it assigns recipes to cooks (workers), keeps track of who cooked what (audit logs), and makes sure the kitchen doesn't catch fire (resource limits). Without it, you're yelling orders across the room and hoping nobody burns the soup.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You've automated your infrastructure with Ansible. Now you have 50 playbooks, 10 engineers, and a compliance officer breathing down your neck. Running playbooks from the CLI is a disaster waiting to happen — someone runs the wrong playbook against production at 3 PM on a Friday. AWX (or its enterprise sibling Tower) is the answer. It gives you a GUI, RBAC, job scheduling, and an audit trail. But it's not a magic bullet. I've seen teams deploy AWX and still have jobs fail because they didn't understand how the worker pool works. This guide covers the production setup, common pitfalls, and how to debug when things go sideways. By the end, you'll be able to deploy AWX, configure it for multi-team use, and troubleshoot job failures without panicking.

Why You Need AWX (And When You Don't)

If you're running Ansible from a single laptop, skip this. AWX shines when you have multiple teams, need audit trails, or want to schedule jobs. The problem it solves: chaos. Without AWX, you have playbooks scattered across repos, credentials in plain text, and no way to know who ran what. I've seen a junior engineer run a playbook that wiped a staging database because they had the wrong inventory selected. AWX gives you guardrails: RBAC, credential management, and job history. But it adds complexity. For a small team with 10 playbooks, plain Ansible with a CI/CD pipeline might be simpler. Don't over-engineer.

awx_install_minikube.shDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// io.thecodeforge — DevOps tutorial

# Install AWX on minikube for testing
minikube start --cpus=4 --memory=8192
kubectl create namespace awx
kubectl config set-context --current --namespace=awx

# Install AWX Operator (latest version)
kubectl apply -f https://raw.githubusercontent.com/ansible/awx-operator/release-2.5.0/deploy/awx-operator.yaml

# Wait for operator to be ready
kubectl wait --for=condition=available deployment/awx-operator --timeout=120s

# Create AWX instance
cat <<EOF | kubectl apply -f -
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-demo
spec:
  service_type: nodeport
  ingress_type: none
  postgres_storage_class: standard
  postgres_storage_requirements:
    requests:
      storage: 8Gi
EOF

# Wait for deployment
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=awx-demo --timeout=300s

# Get admin password
kubectl get secret awx-demo-admin-password -o jsonpath='{.data.password}' | base64 --decode
Output
admin password: <random string>
Production Trap:
Don't install AWX on the same host as your production Ansible targets. If the AWX server goes down, you lose automation. Use a separate cluster or VM.
When to Use AWX vs Plain Ansible
IfTeam size > 5 or multiple teams
UseAWX/Tower
IfNeed audit trail for compliance
UseAWX/Tower
IfScheduled recurring jobs
UseAWX/Tower
IfFewer than 20 playbooks, single admin
UsePlain Ansible + cron

Architecture: The Parts That Matter

AWX is a Django app with a Celery worker pool and a PostgreSQL database. The web frontend talks to the API, which queues jobs in RabbitMQ/Redis. Celery workers pick up jobs and execute Ansible playbooks. The database stores job history, inventories, credentials, and templates. The critical piece: the worker pool. If you have more concurrent jobs than workers, jobs queue up. If you have too many workers, you saturate the database. I've seen teams set AWX_TASK_CONCURRENCY=20 on a 4-core VM — the database couldn't keep up and jobs timed out. Rule of thumb: start with 2 workers per CPU core, then monitor.

awx_deployment_overrides.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// io.thecodeforge — DevOps tutorial

# AWX deployment overrides for production
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-prod
spec:
  # Scale web and task replicas
  web_replicas: 2
  task_replicas: 3
  
  # Resource limits (adjust based on load)
  web_resource_requirements:
    requests:
      cpu: 500m
      memory: 1Gi
    limits:
      cpu: 1
      memory: 2Gi
  task_resource_requirements:
    requests:
      cpu: 1
      memory: 2Gi
    limits:
      cpu: 2
      memory: 4Gi
  
  # Celery worker concurrency
  task_extra_settings:
    - setting: AWX_TASK_CONCURRENCY
      value: "6"  # 2 per CPU core
  
  # PostgreSQL settings
  postgres_resource_requirements:
    requests:
      cpu: 500m
      memory: 2Gi
    limits:
      cpu: 1
      memory: 4Gi
Output
AWX instance 'awx-prod' created with scaled resources.
Senior Shortcut:
Monitor the Celery queue length. If it grows, add workers. If jobs fail with 'DatabaseError: too many connections', reduce concurrency or increase PostgreSQL max_connections.

Inventory Management: Don't Hardcode Hosts

The worst AWX setups I've seen have static inventories with IP addresses hardcoded. That's not automation, that's a glorified spreadsheet. Use dynamic inventories: AWS EC2, Azure, GCP, or custom scripts. AWX supports inventory plugins and custom inventory scripts. The key: keep your inventory source of truth external. When a new VM spins up, AWX should pick it up automatically. I once consulted for a company where the ops team manually added hosts to AWX — they missed a new server and a deployment failed. Use the ansible-inventory command to test your dynamic inventory script before plugging it into AWX.

aws_ec2_inventory.ymlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge — DevOps tutorial

# Dynamic inventory for AWS EC2
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
  - us-west-2
filters:
  tag:Environment:
    - production
    - staging
hostnames:
  - dns-name
  - private-dns-name
compose:
  ansible_host: private_ip_address
keyed_groups:
  - key: tags.Environment
    prefix: env_
  - key: tags.Role
    prefix: role_

# Test with: ansible-inventory -i aws_ec2_inventory.yml --list
Output
{
"_meta": {
"hostvars": {
"ec2-1-2-3-4.compute-1.amazonaws.com": {
"ansible_host": "10.0.1.5",
"tags": {"Environment": "production", "Role": "web"}
}
}
},
"env_production": {
"hosts": ["ec2-1-2-3-4.compute-1.amazonaws.com"]
},
"role_web": {
"hosts": ["ec2-1-2-3-4.compute-1.amazonaws.com"]
}
}
Never Do This:
Don't store AWS access keys in AWX credentials if you can use IAM instance profiles. If your AWX runs on EC2, assign an IAM role — no keys to rotate, no secrets to leak.

Credentials: The Art of Not Leaking Secrets

AWX has a built-in credential store. Use it. Never put passwords in playbook variables. The problem: AWX encrypts credentials at rest, but they're decrypted when passed to the playbook. If a playbook task outputs the variable (e.g., debug: var=my_secret), the secret appears in job logs. Always set no_log: true on tasks that handle secrets. Also, use the 'Hide sensitive data' option in job templates — it redacts output from the web UI. But beware: this only hides from the UI, not the API. If you have API access, you can still see raw output. For true secrets management, integrate with HashiCorp Vault or CyberArk.

playbook_with_secrets.ymlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — DevOps tutorial

---
- name: Deploy application
  hosts: all
  vars:
    db_password: "{{ lookup('awx', 'my_db_password') }}"  # from AWX credential
  tasks:
    - name: Configure database
      ansible.builtin.template:
        src: db_config.j2
        dest: /etc/myapp/db.conf
      no_log: true  # prevents password from appearing in logs
    - name: Restart service
      ansible.builtin.systemd:
        name: myapp
        state: restarted
      no_log: false
Output
TASK [Configure database] ***************************************
ok: [web-01]
TASK [Restart service] ******************************************
changed: [web-01]
The Classic Bug:

Job Templates: The Right Way to Parameterize

Job templates are the core of AWX — they define what playbook to run, against which inventory, with which credentials. The mistake: hardcoding variables in the template. Use survey questions to prompt for variables at runtime. But surveys have a downside: they're static. If you need dynamic options (e.g., list of available branches from Git), use the API to update the survey. Another trap: extra vars. You can pass JSON extra vars in the template, but they override everything. I've seen a team pass { "host_key_checking": false } as extra vars — that's a security risk. Use the 'Ask for extra vars' option sparingly. For complex workflows, use workflow templates.

job_template_survey.jsonDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// io.thecodeforge — DevOps tutorial

// Example survey for a job template (via API)
{
  "name": "Deploy version",
  "description": "Select version to deploy",
  "spec": [
    {
      "question_name": "Application version",
      "question_description": "Git tag or branch to deploy",
      "required": true,
      "type": "text",
      "variable": "app_version",
      "min": 1,
      "max": 128,
      "default": "v1.0.0"
    },
    {
      "question_name": "Environment",
      "question_description": "Target environment",
      "required": true,
      "type": "multiplechoice",
      "variable": "target_env",
      "choices": ["staging", "production"],
      "default": "staging"
    }
  ]
}
Output
Survey created with two questions.
Senior Shortcut:

Scaling Workers: When One Isn't Enough

AWX uses Celery workers to run jobs. By default, it runs one worker process with multiple threads. For high concurrency, you need more worker pods. The gotcha: each worker consumes database connections. If you scale to 10 workers, you might hit PostgreSQL's max_connections limit. Also, workers share the same RabbitMQ queue — if a job is long-running, it blocks others. Solution: use separate queues for different job types. AWX supports custom queues via the instance_groups feature. Assign high-priority jobs to a dedicated queue with more workers.

instance_group_config.ymlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// io.thecodeforge — DevOps tutorial

# Create an instance group for high-priority jobs
# Via AWX CLI (awx-cli)
awx instance_groups create --name "high_priority" --credential 1

# Add a worker instance to the group
awx instance_groups associate --instance worker-1 --instance_group high_priority

# Assign a job template to the group
awx job_templates associate --instance_group high_priority --job_template 5

# Verify
awx instance_groups list
Output
{
"id": 2,
"name": "high_priority",
"instances": ["worker-1"],
"job_templates": [5]
}
Production Trap:

Monitoring and Alerting: Know Before Your Users

AWX has a built-in metrics endpoint at /api/v2/metrics/ (Prometheus format). Expose it and scrape with Prometheus. Key metrics: awx_job_count (total jobs), awx_job_status (success/failure), awx_worker_pool_size. Set alerts for job failures and queue depth. I've seen teams ignore job failures for days because they didn't have monitoring. Also, monitor the AWX database: slow queries can cause timeouts. Use pg_stat_statements to find slow queries. Another blind spot: the AWX web UI itself. If the web pod goes down, users can't launch jobs. Set up a health check endpoint and alert on 5xx responses.

prometheus_alert_rules.ymlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// io.thecodeforge — DevOps tutorial

# Prometheus alert rules for AWX
groups:
  - name: awx
    rules:
      - alert: AWXJobFailureRateHigh
        expr: rate(awx_job_status{status="failed"}[5m]) > 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High job failure rate in AWX"
      - alert: AWXQueueDepthHigh
        expr: awx_queue_depth > 10
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "AWX job queue depth > 10"
      - alert: AWXWebDown
        expr: up{job="awx-web"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "AWX web UI is down"
Output
Three alert rules defined.
Senior Shortcut:

Backup and Disaster Recovery: Because It Will Fail

AWX stores everything in PostgreSQL. Back up the database. But also back up the secrets encryption key (SECRET_KEY in the AWX deployment). Without it, you can't restore encrypted credentials. I've seen a team lose their entire AWX config because they backed up the database but not the secret key. Use the AWX operator's built-in backup feature or a cron job with pg_dump. For Kubernetes deployments, use Velero or similar to backup PVs. Test your restore process quarterly. The worst time to discover your backup is corrupt is during an outage.

awx_backup_cronjob.yamlDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// io.thecodeforge — DevOps tutorial

# Kubernetes CronJob to backup AWX database
apiVersion: batch/v1
kind: CronJob
metadata:
  name: awx-db-backup
  namespace: awx
spec:
  schedule: "0 2 * * *"  # daily at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: pgdump
            image: postgres:15
            command:
            - /bin/sh
            - -c
            - |
              PGPASSWORD=$DB_PASSWORD pg_dump -h $DB_HOST -U $DB_USER awx > /backup/awx-$(date +%Y%m%d).sql
            env:
            - name: DB_HOST
              value: awx-postgres
            - name: DB_USER
              value: awx
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: awx-demo-postgres-configuration
                  key: password
            volumeMounts:
            - name: backup
              mountPath: /backup
          restartPolicy: OnFailure
          volumes:
          - name: backup
            persistentVolumeClaim:
              claimName: awx-backup-pvc
Output
CronJob created. Backups stored in PVC.
Never Do This:
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
AWX web UI became unresponsive, jobs failed with 'ConnectionError: [Errno 111] Connection refused' to the database.
Assumption
Team assumed database was overloaded and tried scaling PostgreSQL.
Root cause
AWX task container had a 4GB memory limit. Under load, the celery worker OOM-killed the container, causing the web process to lose database connections.
Fix
Increased memory limit to 8GB and added --max-memory-per-child=1GB to celery worker args in AWX deployment config.
Key lesson
  • Always monitor container resource limits — the default 4GB is too low for moderate job concurrency.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Job stuck in 'pending' state for >5 minutes
Fix
1. Check worker pod logs: kubectl logs <worker-pod> -c worker 2. Check RabbitMQ queue: kubectl exec <rabbitmq-pod> -- rabbitmqctl list_queues 3. Restart worker pod: kubectl delete pod <worker-pod> 4. If queue is empty but job pending, restart AWX task service: kubectl rollout restart deployment/awx-demo-task
Symptom · 02
Job fails with 'Timeout exceeded'
Fix
1. Increase ANSIBLE_TIMEOUT in job template extra vars (default 30s). 2. Check network connectivity from worker to target hosts. 3. Check if target host is behind a firewall or has SSH issues. 4. Increase AWX job timeout in Settings > Jobs > Job Timeout.
Symptom · 03
Secrets visible in job output
Fix
1. Enable 'Hide sensitive data' in job template. 2. Add no_log: true to tasks that handle secrets. 3. Check if any debug or var tasks expose secrets. 4. Rotate compromised secrets immediately.
★ AWX Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Job stuck pending
Immediate action
Check worker availability
Commands
kubectl get pods -n awx | grep task
kubectl logs <task-pod> -c worker | tail -50
Fix now
kubectl delete pod <task-pod> to restart worker
Database connection errors in logs+
Immediate action
Check PostgreSQL status
Commands
kubectl get pods -n awx | grep postgres
kubectl logs <postgres-pod> | tail -20
Fix now
kubectl rollout restart deployment/awx-demo-postgres
Web UI returns 502+
Immediate action
Check web pod
Commands
kubectl get pods -n awx | grep web
kubectl describe pod <web-pod> | grep -A5 Events
Fix now
kubectl delete pod <web-pod> to restart
Job fails with 'Permission denied' on SSH+
Immediate action
Check credential
Commands
awx credentials list
awx job_templates list --id <id> --json | jq '.summary_fields.credentials'
Fix now
Re-enter SSH private key in credential
FeatureAWX (Open Source)Ansible Tower (Enterprise)
CostFreeLicensed per node
SupportCommunityRed Hat support
RBACBasicAdvanced with teams and roles
Smart InventoriesLimitedFull support
SchedulingYesYes with advanced options
Audit LoggingBasicComprehensive with compliance views
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
awx_install_minikube.shminikube start --cpus=4 --memory=8192Why You Need AWX (And When You Don't)
awx_deployment_overrides.yamlapiVersion: awx.ansible.com/v1beta1Architecture
aws_ec2_inventory.ymlplugin: amazon.aws.aws_ec2Inventory Management
playbook_with_secrets.yml- name: Deploy applicationCredentials
job_template_survey.json{Job Templates
instance_group_config.ymlawx instance_groups create --name "high_priority" --credential 1Scaling Workers
prometheus_alert_rules.ymlgroups:Monitoring and Alerting
awx_backup_cronjob.yamlapiVersion: batch/v1Backup and Disaster Recovery

Key takeaways

1
AWX is the open-source upstream of Red Hat Ansible Automation Platform (Tower is EOL).
2
Install via the AWX Operator on Kubernetes with an external PostgreSQL database.
3
Set max_concurrent_jobs to limit simultaneous playbook runs. Monitor pod resource usage.
4
Use RBAC to control access. Separate organizations per team or environment.

Common mistakes to avoid

3 patterns
×

Running AWX on underpowered hardware

Fix
AWX needs minimum 4GB RAM for testing, 8GB+ for production. Never run it on a 2GB VM alongside other containers.
×

Not using an external database

Fix
Use RDS, Cloud SQL, or Azure Database for production AWX. Containerized Postgres is convenient for testing but risky for production data.
×

Giving all users admin access

Fix
Use AWX RBAC: create organizations, teams, and assign specific permissions per inventory/credential/job template.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does AWX handle concurrent job execution under the hood? What happen...
Q02SENIOR
When would you choose AWX over a CI/CD pipeline (e.g., Jenkins, GitLab C...
Q03SENIOR
What happens when you update a credential in AWX while a job is running?...
Q04JUNIOR
What is the difference between an inventory and a host group in AWX?
Q05SENIOR
A job fails with 'ERROR! Timeout (12s) waiting for privilege escalation ...
Q06SENIOR
How would you design an AWX deployment for a multi-region, multi-team or...
Q01 of 06SENIOR

How does AWX handle concurrent job execution under the hood? What happens when the worker pool is saturated?

ANSWER
AWX uses Celery workers that pull jobs from a RabbitMQ/Redis queue. When all workers are busy, jobs queue up. If the queue grows unbounded, memory pressure can crash the broker. Mitigation: set AWX_TASK_CONCURRENCY and monitor queue depth. Use instance groups to prioritize critical jobs.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Is Ansible Tower still supported in 2026?
02
What are the minimum resource requirements for AWX?
03
Should I use AWX or Ansible Automation Platform?
04
How does RBAC work in AWX?
05
Can AWX schedule playbooks to run automatically?
06
How do I back up and restore AWX?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 11, 2026
last updated
1,727
articles · all by Naren
🔥

That's Ansible. Mark it forged?

3 min read · try the examples if you haven't

Previous
Ansible vs Terraform: When to Use Each
26 / 37 · Ansible
Next
Ansible vs Chef vs Puppet vs SaltStack