Using K3s' Built‑In Traefik Ingress Controller: Setup, Limits and Pitfalls
Learn how to expose services in a K3s cluster with the default Traefik ingress, see a minimal Ingress and TLS example, and understand the constraints and common mistakes to avoid.
18 Feb 2026, 05:13 UTC

Problem: Exposing services without a separate load balancer
When you run a K3s cluster you often need to make HTTP/HTTPS traffic reach your applications from outside the cluster. Installing an external load balancer adds cost and complexity. K3s ships with a ready‑to‑use ingress controller—Traefik—that runs as a hostNetwork pod on each node, listening directly on ports 80 and 443. If you leave Traefik enabled (the default) you can expose services simply by creating an Ingress object.
Useful takeaway
By keeping Traefik enabled and defining an Ingress that references your Service, traffic arriving at any node’s port 80 or 443 is routed to the correct backend without needing an external load balancer.
How Traefik works in K3s
K3s launches Traefik as a DaemonSet‑like pod in the kube-system namespace. The pod spec sets hostNetwork: true, so the container shares the node’s network stack and binds to the node’s IP on ports 80 and 443. Traefik watches the Kubernetes Ingress API and updates its internal routing table automatically.
Minimal working example
- Deploy a test service (run as a user with
kubectlaccess to the cluster):kubectl create deployment nginx --image=nginx:stable kubectl expose deployment nginx --port=80 --target-port=80 - Create a simple Ingress that sends all host‑based traffic to the nginx service:
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress annotations: # optional: redirect HTTP to HTTPS if you later add TLS ingress.kubernetes.io/redirect-entry-point: https spec: rules: - host: example.local # replace with a name you can resolve to the node IP http: paths: - path: / pathType: Prefix backend: service: name: nginx port: number: 80 EOF - Verify from a workstation (you need network reachability to the node’s IP):
You should see the nginx welcome page. If you get a connection refused, check that Traefik is running and that the Ingress was accepted.# Replace with the IP of any node in the cluster curl -v http://example.local/ # add -H "Host: example.local" if DNS is not set
Adding TLS (optional)
Traefik will serve HTTPS only if a TLS secret exists in the same namespace as the Ingress and is referenced by the Ingress tls block.
- Create a TLS secret (replace
tls.crtandtls.keywith your own certificate and key):kubectl create secret tls nginx-tls \ --cert=path/to/tls.crt \ --key=path/to/tls.key \ -n default - Update the Ingress to use the secret:
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress-tls annotations: ingress.kubernetes.io/force-ssl-redirect: "true" spec: tls: - hosts: - example.local secretName: nginx-tls rules: - host: example.local http: paths: - path: / pathType: Prefix backend: service: name: nginx port: number: 80 EOF - Check the result:
Look forcurl -kI https://example.local/ # -k skips cert verification if using a self‑signed certHTTP/2 200and the server header showing Traefik.
Limits and common mistakes
- Traefik is the only default ingress. If you disable it with
--disable traefikyou must install another ingress controller or expose services viatype: LoadBalancerServices; otherwise external traffic will fail. - HostNetwork mode requires privileged access. The Traefik pod runs with
hostNetwork: true, which some pod security policies or network policies may block. Verify that the policy allows traffic to ports 80 and 443 on the host. - TLS secret must be in the same namespace as the Ingress. If you create the secret in a different namespace, Traefik will ignore it and fall back to HTTP, leading to “certificate not trusted” warnings in browsers.
- Image version is tied to the K3s release. Upgrading K3s may upgrade Traefik automatically. Custom annotations or middleware that relied on an older Traefik API version can break. Review the Traefik release notes when you upgrade K3s.
- Advanced features need explicit configuration. Rate limiting, request headers modification, or custom middleware must be defined in a Traefik ConfigMap or via annotations; missing configuration results in the feature being disabled silently.
- Port conflicts on the node. If another process already binds to port 80 or 443 on the node, Traefik will fail to start. Check with
ss -tlnp | grep ':80\|:443'before deploying K3s or change the host ports via the--traefik-argflag (advanced, not covered here).
Practical verification steps
- Confirm Traefik is running:
Look forkubectl get pod -n kube-system -l app.kubernetes.io/name=traefik -o wideREADY 1/1and theHOSTNETWORKcolumn showingtrue(you can also view the spec with-o yaml). - Check the image tag (helps you know which Traefik version you are running):
kubectl get pod -n kube-system -l app.kubernetes.io/name=traefik -o jsonpath='{.items[0].spec.containers[0].image}' - Test routing as shown in the minimal example above; a successful HTTP 200 response indicates Traefik is accepting traffic and forwarding it to the Service.
- Validate TLS (if you added a secret):
The output should show the certificate you created, not a self‑signed Traefik default.curl -vI https://example.local/ 2>&1 | grep -i "certificate" - Inspect logs for errors:
Look for lines likekubectl logs -n kube-system -l app.kubernetes.io/name=traefik --tail=20Starting provider *******or error messages about binding ports.
Rollback considerations
Disabling Traefik changes cluster state: external ingress stops working unless you replace it. To rollback, simply remove the --disable traefik flag from the K3s service/restart the K3s agent, which will redeploy the default Traefik pod. No data loss occurs because Traefik does not store persistent state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.