aly badawy/homelab
all systems operational
// cluster · ingress

Ingress (nginx)

ingress-nginx is the cluster-wide default IngressClass. It terminates TLS at the controller level using the shared wildcard cert — no per-Ingress tls[] blocks, no cert-manager.io/cluster-issuer annotations.

Healthy namespace: networking default IngressClass wildcard TLS

k3s ships with Traefik enabled by default — this cluster disables it and installs ingress-nginx instead. ingress-nginx is installed on k3s via a ServiceLB LoadBalancer Service, which binds ports 80 and 443 directly to the node IP. The default-ssl-certificate flag tells nginx to use wildcard-tls for every HTTPS connection whose SNI hostname isn't matched by a per-Ingress cert (which is every connection, since no per-Ingress certs exist).

01 Key configuration

k8s/components/ingress-nginx/values.yaml yaml
controller:
  ingressClassResource:
    default: true  # cluster-wide default IngressClass

  extraArgs:
    default-ssl-certificate: networking/wildcard-tls  # wildcard cert for all vhosts

  service:
    type: LoadBalancer      # fulfilled by k3s ServiceLB (klipper-lb)
    externalTrafficPolicy: Local  # preserve real client IPs

  config:
    use-forwarded-headers: "true"
    ssl-protocols: "TLSv1.2 TLSv1.3"  # no TLS 1.0/1.1
    client-max-body-size: "100m"  # needed for Nextcloud / Immich uploads

02 What an Ingress looks like

Because TLS is handled at the controller level, every Ingress in this cluster is clean and minimal — just routing rules and a redirect annotation:

k8s/components/whoami/ingress.yaml yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: whoami
  namespace: whoami
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    # no cert-manager.io/cluster-issuer — not needed
spec:
  ingressClassName: nginx
  # no tls[] block — wildcard cert is served automatically
  rules:
    - host: whoami.in.alybadawy.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: whoami
                port:
                  number: 80

03 Adding a new service

Copy the pattern above. The only required annotations are the two ssl-redirect ones. The wildcard cert covers any *.in.alybadawy.com hostname automatically.

Don't add a tls[] block. If you add a tls[] section referencing a non-existent secret, nginx will fall back to a self-signed cert for that host. If you add cert-manager.io/cluster-issuer, cert-manager will try to create a redundant per-hostname cert. Both are unnecessary and break the clean architecture.

04 ArgoCD special case

ArgoCD's server runs in --insecure mode because nginx handles TLS termination. Without this, you get double-TLS: nginx decrypts HTTPS, then tries to re-encrypt to the backend, which causes connection failures with ArgoCD's gRPC. The backend-protocol: HTTP annotation tells nginx to use plain HTTP to the backend:

k8s/components/argocd/values.yaml (ingress section) yaml
server:
  ingress:
    annotations:
      nginx.ingress.kubernetes.io/backend-protocol: HTTP  # nginx → argocd: plain HTTP
      nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    tls: false  # no per-ingress cert; wildcard handles it
configs:
  params:
    server.insecure: true  # argocd-server: accept plain HTTP from nginx
last updated 2026-06-08 · view source on GitHub