Skip to content

ALB Patterns

CWIQ.IO uses internet-facing Application Load Balancers for services that require public HTTPS access. Currently deployed for GitLab (shared-services) and Authentik (NLB). Most other services are accessible only via Tailscale.


GitLab ALB (shared-gitlab-alb)

The GitLab ALB is the primary example of the CWIQ.IO ALB pattern.

Architecture

Internet
AWS ALB (internet-facing, public subnets 10.0.1.x, 10.0.2.x)
  │  HTTPS:443 — SSL terminated at ALB (ACM certificate)
  │  HTTP:80  — redirected to HTTPS (HTTP 301)
GitLab EC2 (private subnet 10.0.10.64/26)
  Port 80 (HTTP — ALB handles TLS)
  ├── monitoring_whitelist: 10.0.0.0/8 (required for ALB health checks)
  └── Puma boot time: ~70 seconds (ALB returns 503 during startup)

ALB Configuration

Attribute Value
Name shared-gitlab-alb
Scheme internet-facing (public)
Subnets 10.0.1.0/24 (us-west-2a), 10.0.2.0/24 (us-west-2b) — public subnets
SSL Policy ELBSecurityPolicy-TLS13-1-2-2021-06
Certificate Imported from Let's Encrypt via cert-server → ACM
Session Stickiness lb_cookie, 24 hours (required for Authentik SSO OAuth flows)

Target Group

Attribute Value
Target Port 80 (HTTP — ALB does SSL termination)
Health Check Path /-/health
Health Check Protocol HTTP
Healthy Threshold 2
Unhealthy Threshold 3
Interval 30 seconds
Deregistration Delay 30 seconds

Listeners

Listener Port Action
HTTPS 443 Forward to shared-gitlab-tg
HTTP 80 Redirect to HTTPS (301)

GitLab Pages Routing (wiki.shared.cwiq.io)

GitLab Pages are served at wiki.shared.cwiq.io using hostname-based routing on the same ALB:

Attribute Value
Certificate Separate ACM cert for wiki.shared.cwiq.io (attached via SNI)
Listener Rule Host header = wiki.shared.cwiq.io → forward to pages target group
Pages Target Port 8090 (GitLab Pages daemon)
Rule Priority 10 (evaluated before default rule)

This allows a single ALB to serve both gitlab.shared.cwiq.io (main app, port 80) and wiki.shared.cwiq.io (Pages, port 8090) with separate SSL certificates via SNI.


SSL Certificate Management for ALBs

ALB certificates must be in ACM (AWS Certificate Manager). CWIQ.IO uses Let's Encrypt certificates managed by the cert-server playbook:

  1. cert-server renews Let's Encrypt cert for the domain
  2. cert-server/acm-import.yml imports the cert to ACM in us-west-2
  3. Terraform references the ACM certificate ARN or uses a data source lookup by domain
# Import cert to ACM (run from ansible server)
ansible-playbook acm-import.yml -e "cert_domain=gitlab.shared.cwiq.io"

Health Check Considerations

GitLab monitoring_whitelist

GitLab's monitoring_whitelist must include 10.0.0.0/8 to allow ALB health checks from the ALB's private IPs. Without this, the ALB target shows unhealthy even when GitLab is running.

GitLab Puma boot time

GitLab Puma takes ~67-70 seconds to boot. The ALB health check will return 503 during this window. If 503 persists beyond 2 minutes after container start, check target health:

aws elbv2 describe-target-health \
  --target-group-arn <tg-arn> \
  --profile shared-services


When to Use an ALB vs Tailscale-Only Access

Service Type Access Pattern Why
GitLab Public ALB External developers, CI integrations, webhooks from GitHub/etc.
Authentik Internal NLB Apps redirect to SSO; accessed via Tailscale subnet router (SNAT)
Nexus Tailscale-only Internal artifact registry; CI runners reach via VPC peering
Grafana Tailscale-only Operations team only, no public exposure needed
Vault Tailscale-only Secrets must not be internet-accessible
SonarQube Tailscale + VPC peering CI runners (EKS pods) use VPC private IP; humans use Tailscale

Terraform Source

terraform-plan/organization/environments/shared-services/ec2-instances/gitlab/
├── alb.tf       ← ALB, target groups, listeners, Pages routing, SNI certs
├── route53.tf   ← Alias records pointing to ALB DNS name
└── variables.tf ← alb_cert_arn, alb_cert_domain, gitlab_pages_enabled, etc.