Skip to content

Handling HTTP and HTTPS with an Application Load Balancer and Traefik

When you install K3s with the default configuration, it typically includes the Traefik ingress controller. This component listens on ports 80 and 443 inside the cluster for incoming HTTP and HTTPS connections. By default, K3s might expose Traefik via a Service of type LoadBalancer or NodePort, depending on your configuration. Below, we’ll assume NodePort and demonstrate a manually created Application Load Balancer that forwards external traffic to Traefik’s NodePorts on each cluster node.

Tip: If you prefer a more automated approach, you can use the AWS Load Balancer Controller for Kubernetes, which automatically creates or configures an ALB when you define a Service of type LoadBalancer. The manual steps below are helpful for learning or more custom setups.


1. Confirm That Traefik Is Installed

1.1 Check if Traefik is running in your K3s cluster

kubectl get pods -A | grep traefik

You should see one or more pods named something like traefik-xxxxxx.

1.2 Confirm Traefik’s Service

kubectl get svc -A | grep traefik

Look for a Service named traefik in the kube-system or traefik namespace. It might be:

  • type: NodePort, or
  • type: LoadBalancer (in which case AWS may already have created an ALB for you).

If you see type: NodePort, take note of the NodePort values for ports 80 and 443. For example:

NAME      TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)                      AGE
traefik   NodePort   10.43.156.164   <none>       80:31380/TCP, 443:31443/TCP   1d
  • HTTP is on port 31380.
  • HTTPS is on port 31443.

If the service is already LoadBalancer, you may not need to create an ALB manually—K3s might have done it (depending on your cluster’s cloud controller). You can skip to setting up DNS and certificates.


2. Open the NodePort Range in the Security Group

Since you’ll forward traffic from the ALB to each node’s NodePort, make sure your cluster’s Security Group rules allow inbound traffic on those ports (e.g., 31380 and 31443, or whichever NodePort values Traefik is using).

Example (assuming the same security group $SG_ID for your nodes):

# Allow the NodePort range or specific NodePorts
aws ec2 authorize-security-group-ingress \
    --group-id $SG_ID \
    --protocol tcp \
    --port 31380 \
    --source-group $SG_ID

aws ec2 authorize-security-group-ingress \
    --group-id $SG_ID \
    --protocol tcp \
    --port 31443 \
    --source-group $SG_ID

If multiple nodes exist, the ALB needs to connect to each node’s NodePort. Opening these ports within the same security group ensures the load balancer instances can communicate with the cluster nodes.


3. Create an Application Load Balancer (ALB)

You will create two listeners on the ALB:

  • Listener on port 80 (HTTP), forwarding to Traefik’s NodePort for HTTP.
  • Listener on port 443 (HTTPS), forwarding to Traefik’s NodePort for HTTPS.

[!TIP] If you plan to let Traefik handle TLS certificates (e.g., Let’s Encrypt), you can do a TCP pass-through of HTTPS traffic or simply forward port 443 with an HTTP target group. Alternatively, you can terminate TLS at the ALB itself (upload certificates to AWS Certificate Manager) and forward HTTP to Traefik inside the cluster. We’ll show a simple pass-through approach below so Traefik can manage certs.

3.1 Create the ALB Itself

LB_ARN=$(aws elbv2 create-load-balancer \
  --name memvergeai-alb \
  --type application \
  --scheme internet-facing \
  --subnets $SUBNET_ID \
  --security-groups $SG_ID \
  --query 'LoadBalancers[0].LoadBalancerArn' \
  --output text)

echo "Created Application Load Balancer ARN: $LB_ARN"
  • We use --scheme internet-facing to allow external internet traffic.
  • Attach it to your existing subnet ($SUBNET_ID) and security group ($SG_ID).

3.2 Create Target Groups for HTTP (NodePort) and HTTPS (NodePort)

We’ll create two distinct target groups: one for port 31380 (HTTP) and one for port 31443 (HTTPS). Note these NodePort values should match the actual ones reported by the Traefik service in Step 1.

3.2.1 Target Group for HTTP

TG_HTTP_ARN=$(aws elbv2 create-target-group \
  --name memvergeai-traefik-http \
  --protocol HTTP \
  --port 31380 \
  --target-type instance \
  --vpc-id $VPC_ID \
  --query 'TargetGroups[0].TargetGroupArn' \
  --output text)

echo "Created HTTP Target Group: $TG_HTTP_ARN"

3.2.2 Target Group for HTTPS

TG_HTTPS_ARN=$(aws elbv2 create-target-group \
  --name memvergeai-traefik-https \
  --protocol TCP \
  --port 31443 \
  --target-type instance \
  --vpc-id $VPC_ID \
  --query 'TargetGroups[0].TargetGroupArn' \
  --output text)

echo "Created HTTPS Target Group: $TG_HTTPS_ARN"

[!NOTE] Why different protocols?

  • HTTP on port 31380: We use ALB’s HTTP protocol for health checks and normal traffic.
  • HTTPS pass-through on port 31443: Because we want Traefik to handle TLS, we set the ALB listener to TCP or “TLS” mode for direct pass-through. If you prefer to terminate TLS at the ALB, you’d choose HTTPS protocol and supply an ACM certificate.

4. Register Cluster Nodes to Each Target Group

All nodes that run the Traefik pods (often every node in a small K3s cluster) should be registered:

# Grab instance IDs for each node (management + worker if they both run Traefik)
INSTANCE_IDS=$(aws ec2 describe-instances \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query "Reservations[].Instances[].InstanceId" \
  --output text)

# Register each instance to the HTTP target group
aws elbv2 register-targets \
  --target-group-arn $TG_HTTP_ARN \
  --targets $(for i in $INSTANCE_IDS; do echo "Id=$i,Port=31380"; done)

# Register each instance to the HTTPS target group
aws elbv2 register-targets \
  --target-group-arn $TG_HTTPS_ARN \
  --targets $(for i in $INSTANCE_IDS; do echo "Id=$i,Port=31443"; done)

[!NOTE] This step is manual. If you later add or remove nodes, you must update the target groups. An alternative approach is to use the AWS Load Balancer Controller which auto-registers new nodes.


5. Create Listeners on the ALB (Ports 80 and 443)

Now we configure how the ALB receives traffic from the internet and forwards to the NodePorts:

5.1 HTTP Listener (Port 80)

aws elbv2 create-listener \
  --load-balancer-arn $LB_ARN \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=$TG_HTTP_ARN
  • Requests arriving at http://<ALB_DNS_NAME> on port 80 get forwarded to Traefik’s HTTP NodePort (31380).

5.2 HTTPS Listener (Port 443)

If you want the ALB to simply pass through HTTPS traffic to Traefik for TLS termination:

aws elbv2 create-listener \
  --load-balancer-arn $LB_ARN \
  --protocol TCP \
  --port 443 \
  --default-actions Type=forward,TargetGroupArn=$TG_HTTPS_ARN

Note: An Application Load Balancer typically works at Layer 7 (HTTP/HTTPS). However, if you want to offload TLS at Traefik, you can choose the ALB’s “TLS” or “TCP” listener. “TLS” is layer 4 pass-through for the ALB.
If you prefer the ALB to terminate TLS, you’d use --protocol HTTPS with an ACM certificate and forward HTTP to port 31443. In that scenario, you’d configure separate health checks and possibly different Ingress settings in Traefik.


6. Verify the ALB and Traefik Connectivity

Retrieve the ALB DNS Name

LB_DNS=$(aws elbv2 describe-load-balancers \
  --load-balancer-arns $LB_ARN \
  --query "LoadBalancers[0].DNSName" \
  --output text)
echo "Your ALB DNS is: $LB_DNS"

Check Target Health

aws elbv2 describe-target-health --target-group-arn $TG_HTTP_ARN
aws elbv2 describe-target-health --target-group-arn $TG_HTTPS_ARN

The instances should report as healthy once Traefik is listening on those NodePorts.

Test HTTP

curl -I http://$LB_DNS

If there is a default HTTP route in Traefik or a default Ingress route, you should see an HTTP 200 or similar response.

If using pass-through for HTTPS, open https://$LB_DNS in a browser. Traefik should respond, though you might get a self-signed certificate error unless you’ve configured Let’s Encrypt or another TLS certificate in Traefik.


7. Create or Update DNS Records

Like in previous steps, you can create an A record or CNAME in Route53 (or your preferred DNS provider) pointing your chosen domain or subdomain (e.g., demo.example.com) to the ALB’s DNS name:

aws route53 change-resource-record-sets \
   --hosted-zone-id $HOSTED_ZONE_ID \
   --change-batch '{
     "Changes": [{
       "Action": "UPSERT",
       "ResourceRecordSet": {
         "Name": "demo.example.com.",
         "Type": "A",
         "AliasTarget": {
           "HostedZoneId": "Z26RNL4JYFTOTI",  # ALB HostedZoneId for your region
           "DNSName": "'"$LB_DNS"'",
           "EvaluateTargetHealth": false
         }
       }
     }]
   }'

Replace demo.example.com with your desired host name.


8. Define Ingress Resources in K3s

To route traffic to your internal services, you’ll create a Kubernetes Ingress resource. For example:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-web-app
  namespace: default
spec:
  rules:
  - host: demo.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-web-service
            port:
              number: 80

Apply it:

kubectl apply -f my-web-app-ingress.yaml
  • Traefik will see this Ingress definition and route demo.example.com traffic to the service named my-web-service in port 80.
  • If you have TLS configured with Let’s Encrypt in Traefik, you can add a tls: block so Traefik presents a valid certificate.

9. Confirm You Can Access Applications

Visit http://demo.example.com or https://demo.example.com (depending on your TLS setup). The traffic flow is:

BrowserALB (80/443) → NodePort on each K3s node → TraefikYour Application

  • If you offloaded TLS at the ALB, the ALB terminates HTTPS, then sends HTTP to Traefik on NodePort.
  • If you are passing TLS through to Traefik, Traefik must have a certificate (e.g., from Let’s Encrypt or self-signed).

10. Scaling and Additional Management Nodes

If you add more K3s nodes (management or worker) that run the Traefik pods, you’ll need to register those node instances with each target group (HTTP and HTTPS). If you prefer an automatic approach, consider the AWS Load Balancer Controller which syncs cluster nodes with the ALB automatically.


Summary

  1. Keep Traefik as the in-cluster ingress controller.
  2. Create an ALB with two listeners (80 & 443).
  3. Forward traffic from each listener to NodePort target groups that map to Traefik’s NodePort range.
  4. Register all relevant node instance IDs so the ALB can distribute traffic across them.
  5. Create a DNS entry pointing your domain or subdomain at the ALB.
  6. Define Kubernetes Ingress objects that route to your services.
  7. Test externally via HTTP/HTTPS.