Introduction
When migrating Ansible Tower or AWX to a new domain (e.g., from tower.oldcorp.com to tower.newcorp.com), the reverse proxy configuration may retain references to the old canonical hostname. This causes HTTP redirects to the old domain, broken API client connections, and certificate validation failures. The issue often persists even after updating the primary Tower configuration because the canonical host setting is cached in multiple places: nginx configuration, Django settings, HAProxy, load balancers, and Tower's internal database.
This typically occurs during: - Corporate rebranding or domain changes - Migrating from on-premises to cloud-hosted Tower - Setting up Tower in a new network segment - Changing from HTTP to HTTPS with a new domain
Symptoms
API clients receive redirects to the old domain:
```bash $ curl -L -k https://tower.newcorp.com/api/v2/ping/ HTTP 301 Moved Permanently Location: https://tower.oldcorp.com/api/v2/ping/
curl: (6) Could not resolve host: tower.oldcorp.com ```
Browser access shows certificate errors:
``` Your connection is not private NET::ERR_CERT_COMMON_NAME_INVALID
This certificate is for: tower.oldcorp.com You are trying to access: tower.newcorp.com ```
In nginx access logs:
# /var/log/nginx/access.log
10.0.1.100 - - [15/Mar/2024:14:23:45 +0000] "GET /api/v2/ping/ HTTP/1.1" 301 178 "-" "curl/7.68.0"
10.0.1.101 - - [15/Mar/2024:14:24:12 +0000] "GET / HTTP/1.1" 302 145 "https://tower.newcorp.com/" "Mozilla/5.0"Tower API returns incorrect URLs in responses:
{
"id": 1,
"type": "job_template",
"url": "/api/v2/job_templates/1/",
"related": {
"jobs": "/api/v2/job_templates/1/jobs/",
"labels": "/api/v2/job_templates/1/labels/"
},
"name": "Deploy Application",
"webhook_credential": null,
"webhook_key": "https://tower.oldcorp.com/api/v2/job_templates/1/webhook_key/"
}In Tower callback receiver logs:
2024-03-15 14:23:45,123 ERROR awx.main.callbacks Callback receiver failed: HTTPSConnectionPool(host='tower.oldcorp.com', port=443)
2024-03-15 14:24:12,456 WARNING awx.main.dispatch Job event submission failed: Name or service not knownAnsible CLI clients fail:
$ tower-cli job launch --job-template=1
Error: Failed to establish connection to https://tower.oldcorp.com/
Please verify TOWER_HOST is set correctlyCommon Causes
The old canonical host persists due to configuration being stored in multiple layers:
- 1.nginx server_name directive: The nginx configuration explicitly lists
server_name tower.oldcorp.com, causing it to respond only to that hostname and redirect others. - 2.Tower's TOWER_URL setting: Tower stores its base URL in
settings.pyand uses it to generate absolute URLs in API responses, webhook URLs, and callback endpoints. - 3.Database-stored URLs: Some Tower objects store absolute URLs in the database during creation (webhook keys, callback URLs, notification templates).
- 4.Load balancer or HAProxy health checks: Health checks may be configured against the old hostname, causing failures after the cutover.
- 5.SSL certificate SAN entries: The SSL certificate may only include the old domain in its Subject Alternative Names, causing browser warnings.
- 6.Cache and browser history: Browsers and HTTP caches may have cached redirects or HSTS settings for the old domain.
- 7.Instance hostname in Tower database: Tower stores each instance's hostname in the database, which may still reference the old domain.
Step-by-Step Fix
Step 1: Identify All References to Old Domain
Find all configuration files containing the old hostname:
```bash OLD_DOMAIN="tower.oldcorp.com" NEW_DOMAIN="tower.newcorp.com"
# Search Tower configuration grep -r "$OLD_DOMAIN" /etc/tower/ grep -r "$OLD_DOMAIN" /etc/nginx/ grep -r "$OLD_DOMAIN" /etc/haproxy/
# Search Tower database for absolute URLs sudo -u postgres psql -d awx -c " SELECT table_name, column_name FROM information_schema.columns WHERE column_name LIKE '%url%' OR column_name LIKE '%host%';"
# Find URLs in database sudo -u postgres psql -d awx -c " SELECT 'main_jobtemplate' as tbl, id, webhook_key FROM main_jobtemplate WHERE webhook_key LIKE '%$OLD_DOMAIN%';"
# Check nginx configuration nginx -T 2>/dev/null | grep -i "server_name"
# Check Tower settings grep -E "TOWER_URL|SITE_URL|ALLOWED_HOSTS" /etc/tower/settings.py ```
Step 2: Update Tower Configuration
Update the primary Tower settings:
```bash # Backup current configuration sudo cp /etc/tower/settings.py /etc/tower/settings.py.bak
# Update settings sudo sed -i "s/$OLD_DOMAIN/$NEW_DOMAIN/g" /etc/tower/settings.py
# Or manually edit sudo vim /etc/tower/settings.py
# Update these settings: TOWER_URL = 'https://tower.newcorp.com' SITE_NAME = 'tower.newcorp.com'
ALLOWED_HOSTS = [ 'localhost', '127.0.0.1', 'tower.newcorp.com', 'tower01.newcorp.com', 'tower02.newcorp.com', ]
# CSRF and cookie settings CSRF_TRUSTED_ORIGINS = ['https://tower.newcorp.com'] SESSION_COOKIE_DOMAIN = '.newcorp.com' # For SSO across subdomains
# Webhook base URL AWX_WEBHOOK_BASE_URL = 'https://tower.newcorp.com/api/v2/webhook/' ```
Step 3: Update nginx Configuration
Fix the nginx virtual host configuration:
```bash # Edit nginx configuration sudo vim /etc/nginx/nginx.conf
# Or edit the Tower-specific config sudo vim /etc/nginx/conf.d/tower.conf
# Update server_name directive server { listen 80; listen 443 ssl; server_name tower.newcorp.com tower01.newcorp.com tower02.newcorp.com;
# Update SSL certificate (if using new cert) ssl_certificate /etc/ssl/certs/tower.newcorp.com.crt; ssl_certificate_key /etc/ssl/private/tower.newcorp.com.key;
# Remove or update any hardcoded redirect # return 301 https://tower.oldcorp.com$request_uri; # REMOVE THIS
# Update proxy headers if needed location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; } }
# Test nginx configuration sudo nginx -t
# Reload nginx sudo systemctl reload nginx ```
Step 4: Update Database-Stored URLs
Update URLs stored in Tower's database:
```bash # Connect to Tower database sudo -u postgres psql -d awx
# Start a transaction BEGIN;
-- Update webhook URLs in job templates UPDATE main_jobtemplate SET webhook_key = REPLACE(webhook_key, 'tower.oldcorp.com', 'tower.newcorp.com') WHERE webhook_key LIKE '%tower.oldcorp.com%';
-- Update callback URLs UPDATE main_job SET job_events = REPLACE(job_events::text, 'tower.oldcorp.com', 'tower.newcorp.com')::json WHERE job_events::text LIKE '%tower.oldcorp.com%';
-- Update notification templates UPDATE main_notificationtemplate SET notification_configuration = REPLACE(notification_configuration::text, 'tower.oldcorp.com', 'tower.newcorp.com')::json WHERE notification_configuration::text LIKE '%tower.oldcorp.com%';
-- Update instance hostnames UPDATE main_instance SET hostname = REPLACE(hostname, 'tower.oldcorp.com', 'tower.newcorp.com') WHERE hostname LIKE '%tower.oldcorp.com%';
-- Verify changes SELECT hostname FROM main_instance; SELECT webhook_key FROM main_jobtemplate WHERE webhook_key IS NOT NULL LIMIT 5;
-- Commit if correct COMMIT; -- Or rollback if issues -- ROLLBACK; ```
Step 5: Update SSL Certificate
Install the correct certificate for the new domain:
```bash # Generate new CSR for new domain openssl req -new -newkey rsa:2048 -nodes \ -keyout /etc/ssl/private/tower.newcorp.com.key \ -out /etc/ssl/certs/tower.newcorp.com.csr \ -subj "/C=US/ST=State/L=City/O=Organization/CN=tower.newcorp.com" \ -addext "subjectAltName = DNS:tower.newcorp.com,DNS:tower01.newcorp.com,DNS:tower02.newcorp.com"
# Submit CSR to your CA # After receiving the signed certificate, install it sudo cp tower.newcorp.com.crt /etc/ssl/certs/ sudo cp tower.newcorp.com.key /etc/ssl/private/
# Set proper permissions sudo chmod 600 /etc/ssl/private/tower.newcorp.com.key sudo chmod 644 /etc/ssl/certs/tower.newcorp.com.crt
# Update certificate symlinks if Tower uses them sudo ln -sf /etc/ssl/certs/tower.newcorp.com.crt /etc/tower/tower.cert sudo ln -sf /etc/ssl/private/tower.newcorp.com.key /etc/tower/tower.key
# Verify certificate openssl x509 -in /etc/ssl/certs/tower.newcorp.com.crt -text -noout | grep -A1 "Subject Alternative Name" ```
Step 6: Update Load Balancer Configuration
If using an external load balancer:
```bash # For HAProxy sudo vim /etc/haproxy/haproxy.cfg
frontend tower_https bind *:443 ssl crt /etc/ssl/tower.newcorp.com.pem acl is_tower hdr(host) -i tower.newcorp.com tower01.newcorp.com redirect prefix https://tower.newcorp.com code 301 if !is_tower default_backend tower_servers
backend tower_servers balance roundrobin option httpchk HEAD /api/v2/ping/ HTTP/1.1\r\nHost:\ tower.newcorp.com server tower01 10.0.1.10:443 ssl check verify none server tower02 10.0.1.11:443 ssl check verify none
sudo systemctl reload haproxy
# For AWS ALB aws elbv2 modify-listener \ --listener-arn arn:aws:elasticloadbalancing:region:account:listener/app/tower-alb/xxx/yyy \ --certificates CertificateArn=arn:aws:acm:region:account:certificate/zzz ```
Step 7: Clear Caches and Restart
Clear all caches and restart services:
```bash # Clear Tower cache redis-cli FLUSHDB
# Clear Django cache sudo -u awx awx-manage clear_cache
# Clear static file cache sudo rm -rf /var/lib/awx/public/static/.cache
# Restart Tower services sudo ansible-tower-service restart
# Verify services are running sudo ansible-tower-service status
# Test direct access curl -k https://localhost/api/v2/ping/ ```
Step 8: Update DNS and Verify
Ensure DNS points to the new infrastructure:
```bash # Check DNS resolution dig tower.newcorp.com +short dig tower.oldcorp.com +short # Should not resolve or redirect
# Test from external client curl -L https://tower.newcorp.com/api/v2/ping/ | jq
# Verify no redirects to old domain curl -I https://tower.newcorp.com/ 2>&1 | grep -i location # Should show no redirect or only internal paths ```
Verification
Confirm all components use the new domain:
```bash # Test API returns correct URLs curl -k -u admin:password https://tower.newcorp.com/api/v2/job_templates/1/ | jq '.webhook_key' # Should show: "https://tower.newcorp.com/api/v2/job_templates/1/webhook_key/"
# Test webhook triggers work curl -X POST https://tower.newcorp.com/api/v2/webhook/job_template:1:abc123/
# Verify Tower ping returns correct hostname curl -k https://tower.newcorp.com/api/v2/ping/ | jq '.active_node'
# Launch a test job and verify callbacks work curl -X POST -k -u admin:password https://tower.newcorp.com/api/v2/job_templates/1/launch/ | jq '.job'
# Check job events are being received # (Look for job events in Tower UI or API)
# Verify browser access with no certificate warnings # Open https://tower.newcorp.com/ in browser ```
Related Issues
- [ansible-tower-ssl-certificate-errors](/articles/ansible-tower-ssl-certificate-errors)
- [ansible-webhook-failures-after-url-change](/articles/ansible-webhook-failures-after-url-change)
- [ansible-behind-load-balancer-misconfiguration](/articles/ansible-behind-load-balancer-misconfiguration)
Related Articles
- [WordPress troubleshooting: Ansible Artifact Download Uses an Old Mi](ansible-artifact-download-uses-an-old-mirror-after-proxy-change)
- [WordPress troubleshooting: Ansible Audit Trail Misses Events Under ](ansible-audit-trail-misses-events-under-burst-load)
- [WordPress troubleshooting: Ansible Background Worker Gets Stuck in ](ansible-background-worker-stuck-in-a-retry-loop)
- [WordPress troubleshooting: Ansible Backup Completes but Restore Fai](ansible-backup-completes-but-restore-fails-checksum-validation)
- [WordPress troubleshooting: Ansible Batch Importer Duplicates Rows A](ansible-batch-importer-duplicates-rows-after-a-retry)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "WordPress troubleshooting: Ansible Reverse Proxy Keeps an Old Canon", "description": "Learn how to fix Ansible Reverse Proxy Keeps an Old Canonical Host After Domain Cutover. Professional WordPress troubleshooting solutions with step-by-step guidance. WP error fix, WordPress optimization, WP security, WordPress performance.", "url": "https://www.fixwikihub.com/ansible-reverse-proxy-keeps-an-old-canonical-host-after-domain-cutover", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-03-07T17:01:07.421Z", "dateModified": "2026-03-07T17:01:07.421Z" } </script>