Skip to main content
The Application Load Balancer (ALB) serves as the single entry point for all traffic to the ADMA application, routing requests to the appropriate backend or frontend services.

ALB Architecture

The ALB handles:
  • HTTP to HTTPS redirection on port 80
  • SSL/TLS termination on port 443
  • Path-based routing to frontend and backend services
  • Health checks for both services
  • Sticky sessions (optional for stateful applications)

Routing Strategy

Path PatternTarget ServicePortExample
/api/*Backend8080/api/urls, /api/auth/login
/{shortCode}Backend8080/abc1234 (7-char codes)
/* (default)Frontend80/, /dashboard, /login

Create the Application Load Balancer

1
Get Public Subnet IDs
2
The ALB must be in public subnets to accept internet traffic:
3
# List subnets in your VPC
aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'Subnets[?MapPublicIpOnLaunch==`true`].[SubnetId,AvailabilityZone]' \
  --output table \
  --region $AWS_REGION
4
Select two subnets in different availability zones:
5
export SUBNET_PUBLIC_A="subnet-XXXXXXXX"
export SUBNET_PUBLIC_B="subnet-YYYYYYYY"
6
Create ALB Security Group
7
Create a security group that allows inbound HTTP and HTTPS traffic:
8
ALB_SG=$(aws ec2 create-security-group \
  --group-name adma-alb-sg \
  --description "Security group for ADMA Application Load Balancer" \
  --vpc-id $VPC_ID \
  --region $AWS_REGION \
  --query 'GroupId' \
  --output text)

echo "ALB Security Group: $ALB_SG"

# Allow HTTP (port 80) from anywhere
aws ec2 authorize-security-group-ingress \
  --group-id $ALB_SG \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0 \
  --region $AWS_REGION

# Allow HTTPS (port 443) from anywhere
aws ec2 authorize-security-group-ingress \
  --group-id $ALB_SG \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0 \
  --region $AWS_REGION
9
Create the Load Balancer
10
ALB_ARN=$(aws elbv2 create-load-balancer \
  --name adma-alb \
  --subnets $SUBNET_PUBLIC_A $SUBNET_PUBLIC_B \
  --security-groups $ALB_SG \
  --scheme internet-facing \
  --type application \
  --ip-address-type ipv4 \
  --region $AWS_REGION \
  --query 'LoadBalancers[0].LoadBalancerArn' \
  --output text)

echo "ALB ARN: $ALB_ARN"
11
Get ALB DNS Name
12
The ALB DNS name is your application’s public endpoint:
13
ALB_DNS=$(aws elbv2 describe-load-balancers \
  --load-balancer-arns $ALB_ARN \
  --query 'LoadBalancers[0].DNSName' \
  --output text \
  --region $AWS_REGION)

echo "ALB DNS: $ALB_DNS"
# Example: adma-alb-123456789.eu-west-1.elb.amazonaws.com

Create Target Groups

Target groups define how the ALB routes traffic to your ECS services.

Frontend Target Group

FRONTEND_TG_ARN=$(aws elbv2 create-target-group \
  --name adma-frontend-tg \
  --protocol HTTP \
  --port 80 \
  --vpc-id $VPC_ID \
  --target-type ip \
  --health-check-enabled \
  --health-check-path "/" \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3 \
  --matcher HttpCode=200 \
  --region $AWS_REGION \
  --query 'TargetGroups[0].TargetGroupArn' \
  --output text)

echo "Frontend Target Group ARN: $FRONTEND_TG_ARN"

Backend Target Group

BACKEND_TG_ARN=$(aws elbv2 create-target-group \
  --name adma-backend-tg \
  --protocol HTTP \
  --port 8080 \
  --vpc-id $VPC_ID \
  --target-type ip \
  --health-check-enabled \
  --health-check-path "/actuator/health" \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 10 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3 \
  --matcher HttpCode=200 \
  --region $AWS_REGION \
  --query 'TargetGroups[0].TargetGroupArn' \
  --output text)

echo "Backend Target Group ARN: $BACKEND_TG_ARN"
Target Type ip: ECS Fargate with awsvpc network mode requires IP-based target groups. ECS automatically registers and deregisters task IPs.

Configure Deregistration Delay

Reduce the deregistration delay for faster deployments:
# Frontend - quick deregistration (static files)
aws elbv2 modify-target-group-attributes \
  --target-group-arn $FRONTEND_TG_ARN \
  --attributes Key=deregistration_delay.timeout_seconds,Value=30 \
  --region $AWS_REGION

# Backend - allow in-flight requests to complete
aws elbv2 modify-target-group-attributes \
  --target-group-arn $BACKEND_TG_ARN \
  --attributes Key=deregistration_delay.timeout_seconds,Value=60 \
  --region $AWS_REGION

Configure Security Group Rules

Update ECS security groups to allow traffic from the ALB:
# Allow ALB to reach backend on port 8080
aws ec2 authorize-security-group-ingress \
  --group-id $BACKEND_SG \
  --protocol tcp \
  --port 8080 \
  --source-group $ALB_SG \
  --region $AWS_REGION

# Allow ALB to reach frontend on port 80
aws ec2 authorize-security-group-ingress \
  --group-id $FRONTEND_SG \
  --protocol tcp \
  --port 80 \
  --source-group $ALB_SG \
  --region $AWS_REGION

# Allow backend to reach RDS on port 5432
aws ec2 authorize-security-group-ingress \
  --group-id $RDS_SG \
  --protocol tcp \
  --port 5432 \
  --source-group $BACKEND_SG \
  --region $AWS_REGION

Create Listeners and Routing Rules

HTTP Listener (Port 80) - Redirect to HTTPS

aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=redirect,RedirectConfig="{Protocol=HTTPS,Port=443,StatusCode=HTTP_301}" \
  --region $AWS_REGION
This creates a permanent redirect (301) from HTTP to HTTPS. All traffic will be encrypted.

HTTPS Listener (Port 443) - Main Application Traffic

Before creating the HTTPS listener, you need an SSL/TLS certificate from AWS Certificate Manager. See HTTPS/SSL Setup first, or create the listener without HTTPS temporarily.
# After obtaining ACM certificate
CERT_ARN="arn:aws:acm:$AWS_REGION:$ACCOUNT_ID:certificate/CERTIFICATE_ID"

LISTENER_ARN=$(aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTPS \
  --port 443 \
  --certificates CertificateArn=$CERT_ARN \
  --ssl-policy ELBSecurityPolicy-TLS-1-2-2017-01 \
  --default-actions Type=forward,TargetGroupArn=$FRONTEND_TG_ARN \
  --region $AWS_REGION \
  --query 'Listeners[0].ListenerArn' \
  --output text)

echo "HTTPS Listener ARN: $LISTENER_ARN"

Option 2: Temporary HTTP Listener for Testing

For testing without HTTPS:
LISTENER_ARN=$(aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=$FRONTEND_TG_ARN \
  --region $AWS_REGION \
  --query 'Listeners[0].ListenerArn' \
  --output text)

Add Routing Rules

Create rules to route API and shortcode requests to the backend:
1
Rule 1: Route /api/* to Backend
2
aws elbv2 create-rule \
  --listener-arn $LISTENER_ARN \
  --priority 10 \
  --conditions Field=path-pattern,Values="/api/*" \
  --actions Type=forward,TargetGroupArn=$BACKEND_TG_ARN \
  --region $AWS_REGION
3
Rule 2: Route Short Codes to Backend
4
Short codes are 7 alphanumeric characters. We use path patterns to match them:
5
aws elbv2 create-rule \
  --listener-arn $LISTENER_ARN \
  --priority 20 \
  --conditions Field=path-pattern,Values="/???????" \
  --actions Type=forward,TargetGroupArn=$BACKEND_TG_ARN \
  --region $AWS_REGION
6
Each ? matches exactly one character. /??????? matches 7-character short codes like /abc1234.
7
Rule 3: Default - Route Everything Else to Frontend
8
The default action (configured when creating the listener) already forwards to the frontend target group. This catches all other routes like /, /login, /dashboard.

Create ECS Services

Now that the ALB and target groups exist, create the ECS services:

Backend Service

aws ecs create-service \
  --cluster adma-cluster \
  --service-name adma-backend \
  --task-definition adma-backend:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --platform-version LATEST \
  --network-configuration "awsvpcConfiguration={
    subnets=[$SUBNET_PRIVATE_A,$SUBNET_PRIVATE_B],
    securityGroups=[$BACKEND_SG],
    assignPublicIp=DISABLED
  }" \
  --load-balancers "
    targetGroupArn=$BACKEND_TG_ARN,
    containerName=backend,
    containerPort=8080" \
  --health-check-grace-period-seconds 60 \
  --region $AWS_REGION
Important: Replace $SUBNET_PRIVATE_A and $SUBNET_PRIVATE_B with your private subnet IDs. ECS tasks should not be in public subnets.

Frontend Service

aws ecs create-service \
  --cluster adma-cluster \
  --service-name adma-frontend \
  --task-definition adma-frontend:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --platform-version LATEST \
  --network-configuration "awsvpcConfiguration={
    subnets=[$SUBNET_PRIVATE_A,$SUBNET_PRIVATE_B],
    securityGroups=[$FRONTEND_SG],
    assignPublicIp=DISABLED
  }" \
  --load-balancers "
    targetGroupArn=$FRONTEND_TG_ARN,
    containerName=frontend,
    containerPort=80" \
  --health-check-grace-period-seconds 30 \
  --region $AWS_REGION

Service Parameters Explained

ParameterValueDescription
desired-count2Number of tasks to run (high availability)
launch-typeFARGATEServerless container execution
assignPublicIpDISABLEDTasks in private subnets (accessed via ALB)
health-check-grace-period-seconds60Time before first health check
Backend Scaling: The backend uses ShedLock for distributed job coordination. You can safely scale to 2+ instances without duplicate scheduled task execution.

Verify Deployment

1
Check Service Status
2
aws ecs describe-services \
  --cluster adma-cluster \
  --services adma-backend adma-frontend \
  --query 'services[].[serviceName,status,runningCount,desiredCount]' \
  --output table \
  --region $AWS_REGION
3
Expected output:
4
-----------------------------------------
|           DescribeServices           |
+----------------+---------+----+-------+
|  adma-backend  | ACTIVE  | 2  |   2   |
|  adma-frontend | ACTIVE  | 2  |   2   |
+----------------+---------+----+-------+
5
Check Target Health
6
# Backend targets
aws elbv2 describe-target-health \
  --target-group-arn $BACKEND_TG_ARN \
  --region $AWS_REGION

# Frontend targets
aws elbv2 describe-target-health \
  --target-group-arn $FRONTEND_TG_ARN \
  --region $AWS_REGION
7
All targets should show healthy state.
8
Test the Application
9
# Test backend API
curl http://$ALB_DNS/api/stats

# Test frontend
curl -I http://$ALB_DNS/

Monitoring and Troubleshooting

View ALB Access Logs

Enable access logs to S3 for debugging:
# Create S3 bucket for logs
LOG_BUCKET="adma-alb-logs-$ACCOUNT_ID"
aws s3 mb s3://$LOG_BUCKET --region $AWS_REGION

# Enable access logging
aws elbv2 modify-load-balancer-attributes \
  --load-balancer-arn $ALB_ARN \
  --attributes \
    Key=access_logs.s3.enabled,Value=true \
    Key=access_logs.s3.bucket,Value=$LOG_BUCKET \
  --region $AWS_REGION

Common Issues

Targets Show “Unhealthy”

Causes:
  1. Security group doesn’t allow ALB traffic
  2. Container health check failing
  3. Wrong health check path
Solution:
# Check security group rules
aws ec2 describe-security-groups --group-ids $BACKEND_SG

# Check container logs
aws logs tail /ecs/adma-backend --follow --region $AWS_REGION

503 Service Unavailable

Cause: No healthy targets in the target group Solution: Check ECS service events and task logs:
aws ecs describe-services \
  --cluster adma-cluster \
  --services adma-backend \
  --query 'services[0].events[0:5]' \
  --region $AWS_REGION

Connection Timeout

Cause: Network configuration issue (subnets, NAT gateway, route tables) Solution: Verify private subnets have routes to a NAT Gateway for outbound internet access (required for ECR image pulls and SSM parameter access).

Performance Optimization

Enable Connection Draining

Already configured via deregistration_delay. Ensures in-flight requests complete before stopping old tasks.

Enable Sticky Sessions (Optional)

If your application requires session affinity:
aws elbv2 modify-target-group-attributes \
  --target-group-arn $BACKEND_TG_ARN \
  --attributes \
    Key=stickiness.enabled,Value=true \
    Key=stickiness.type,Value=lb_cookie \
    Key=stickiness.lb_cookie.duration_seconds,Value=86400 \
  --region $AWS_REGION
ADMA uses JWT tokens for authentication, so sticky sessions are not required. The application is stateless.

Next Steps

With the load balancer configured:
  1. Enable HTTPS/SSL - Request ACM certificate and secure your ALB
  2. Configure Route 53 DNS to point to the ALB
  3. Set up Auto Scaling for the ECS services
  4. Enable CloudWatch alarms for monitoring

Build docs developers (and LLMs) love