When working with AWS Application Load Balancer (ALB v2), many developers encounter the default behavior where unhealthy targets simply return 503 errors. While technically accurate, this provides poor user experience during maintenance or unexpected outages.
Here's a practical approach using AWS services:
Resources:
- Primary EC2: Runs your main application (nginx/uwsgi/Django)
- Fallback EC2: Tiny instance (t3.nano) running minimal nginx
- Route 53: For DNS failover routing
- S3: Optional for static maintenance page storage
1. Configure the Fallback Instance:
# Minimal nginx config for maintenance page
server {
listen 80;
server_name _;
location / {
root /var/www/maintenance;
try_files /index.html =503;
}
}
2. ELB Target Groups Setup:
aws elbv2 create-target-group \
--name prod-main \
--protocol HTTP \
--port 80 \
--vpc-id vpc-123456 \
--health-check-path /health-check/
aws elbv2 create-target-group \
--name maintenance \
--protocol HTTP \
--port 80 \
--vpc-id vpc-123456 \
--health-check-path /
Create a Lambda function triggered by CloudWatch Events when health status changes:
import boto3
def lambda_handler(event, context):
elbv2 = boto3.client('elbv2')
# Get current unhealthy targets
response = elbv2.describe_target_health(
TargetGroupArn='arn:aws:elasticloadbalancing:...',
Targets=[{'Id': 'i-1234567890abcdef0'}]
)
if response['TargetHealthDescriptions'][0]['TargetHealth']['State'] == 'unhealthy':
# Update listener rule to redirect to maintenance target group
elbv2.modify_rule(
RuleArn='arn:aws:elasticloadbalancing:...',
Actions=[{
'Type': 'forward',
'TargetGroupArn': 'arn:aws:elasticloadbalancing:...'
}]
)
For more sophisticated maintenance pages that can show estimated downtime:
// CloudFront Lambda@Edge Origin Response trigger
exports.handler = (event, context, callback) => {
const response = event.Records[0].cf.response;
if (response.status === '503') {
response.status = '200';
response.statusDescription = 'OK';
response.body =
<html>
<body>
<h1>Maintenance Notice</h1>
<p>${new Date().toLocaleString()}</p>
<p>Expected back online in 30 minutes</p>
</body>
</html>
;
}
callback(null, response);
};
For static maintenance pages, consider using S3 website hosting with Route 53 failover:
aws s3 cp maintenance.html s3://your-bucket/ --content-type "text/html"
aws s3 website s3://your-bucket/ --index-document maintenance.html
Always verify your failover mechanism works:
# Simulate downtime
sudo systemctl stop nginx
# Test from multiple regions
curl -v http://your-elb-dns
When using AWS Elastic Load Balancer (ELB) v2 with a Django/Nginx stack, you'll notice that during instance failures or maintenance, users simply get connection errors or generic 5xx responses. This creates a poor user experience compared to a friendly maintenance page explaining the situation.
Here's a robust solution using AWS components:
[ELB v2]
├── [Target Group 1] (Primary - Django/Nginx)
│ └── Auto Scaling Group (min: 1, max: 1)
└── [Target Group 2] (Fallback - Static S3)
└── S3 bucket with static error page
1. Create the maintenance page:
<!DOCTYPE html>
<html>
<head>
<title>Maintenance in Progress</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>We're Down for Maintenance</h1>
<p>Our site is currently undergoing scheduled maintenance.</p>
<p>Expected back by: <span id="maintenance-time">3:00 PM UTC</span></p>
</body>
</html>
2. Configure S3 as fallback target:
# AWS CLI commands to set up S3
aws s3 mb s3://your-maintenance-page-bucket
aws s3 cp maintenance.html s3://your-maintenance-page-bucket/index.html
aws s3 website s3://your-maintenance-page-bucket --index-document index.html
For more control, modify your Nginx config to handle maintenance mode:
server {
listen 80;
location / {
if (-f /var/www/maintenance.flag) {
return 503;
}
try_files $uri @django;
}
location @django {
include uwsgi_params;
uwsgi_pass unix:///tmp/uwsgi.sock;
}
error_page 503 @maintenance;
location @maintenance {
root /var/www/maintenance;
rewrite ^(.*)$ /index.html break;
}
}
Create a Lambda function to toggle maintenance mode via CloudWatch alarms:
import boto3
def lambda_handler(event, context):
elbv2 = boto3.client('elbv2')
# Get target group ARNs
primary_tg = 'arn:aws:elasticloadbalancing:...'
fallback_tg = 'arn:aws:elasticloadbalancing:...'
# Modify listener rules based on alarm state
if event['detail']['state'] == 'ALARM':
elbv2.modify_rule(
RuleArn='your-listener-rule-arn',
Actions=[{
'Type': 'forward',
'TargetGroupArn': fallback_tg
}]
)
else:
elbv2.modify_rule(
RuleArn='your-listener-rule-arn',
Actions=[{
'Type': 'forward',
'TargetGroupArn': primary_tg
}]
)
Always test your failover configuration:
# Simulate instance failure
aws autoscaling set-instance-health \
--instance-id i-1234567890abcdef0 \
--health-status Unhealthy