How to set up mutual TLS with Traefik for wildcard domains
In a previous article I described how to configure mutual TLS (mTLS) with Traefik using a basic domain. How can you setup mTLS for wildcard domains? I’ll modify the setup described previously to support a wildcard domain.
First, I will need a certificate with the common name set to a wildcard domain. Previously I used localhost
as a domain. I cannot use *.localhost
for this example because localhost is a special-use domain that tools like curl or browsers treat differently. Instead, I’ll use another domain: *.example.com
.
Let’s generate a server certificate for the *.example.com
wildcard domain. Make sure to specify *.example.com
as the CN (Common Name):
1-> % ./gen_cert.sh --cacrt ca.crt --cakey ca.key -o server
2...
3-> % ./inspect_cert.sh server.crt
4...
5----------------------------------------
6Certificate summary for: server.crt
7Common Name (CN): *.example.com
8Status: Valid (expires on 2035-07-01T13:13:07Z UTC)
9----------------------------------------
Modern certificates use the Subject Alternative Name (SAN) extension to support multiple domains and simplify certificate management. However, for this example, a basic CN-based cert works fine.
To test the wildcard domain locally, I’ll add the foo.example.com
subdomain in the /etc/hosts
file so my machine can resolve it to the localhost address, then test it with a ping:
1-> % cat /etc/hosts | grep example.com
2127.0.0.1 foo.example.com
3::1 foo.example.com
4
5-> % ping foo.example.com -c 3
6PING foo.example.com (127.0.0.1): 56 data bytes
764 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.078 ms
864 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.190 ms
964 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.116 ms
10
11--- foo.example.com ping statistics ---
123 packets transmitted, 3 packets received, 0.0% packet loss
13round-trip min/avg/max/stddev = 0.078/0.128/0.190/0.047 ms
Let’s create a TLS secret in the whoami namespace (will be used by an ingress resource in the whoami namespace):
1-> % kubectl create secret tls server-wildcard-tls-certificate --cert=server.crt --key=server.key -n whoami
2secret/server-wildcard-tls-certificate created
The certificate is available in the cluster. Next, an ingress rule for the wildcard domain must be created, so all requests going to a subdomain of example.com
will be redirected to the whoami service. Let’s create it, apply it and then test it using curl:
1-> % cat whoami-wildcard-ingress.yaml
2apiVersion: networking.k8s.io/v1
3kind: Ingress
4metadata:
5 name: whoami-wildcard
6 namespace: whoami
7spec:
8 ingressClassName: traefik
9 rules:
10 - host: '*.example.com'
11 http:
12 paths:
13 - path: /
14 pathType: Prefix
15 backend:
16 service:
17 name: whoami
18 port:
19 name: whoamiweb
20 tls:
21 - hosts:
22 - '*.example.com'
23 secretName: server-wildcard-tls-certificate
24-> % k apply -f whoami-wildcard-ingress.yaml
25ingress.networking.k8s.io/whoami-wildcard created
26-> % curl --cacert ca.crt https://foo.example.com
27Hostname: whoami
28IP: 127.0.0.1
29IP: ::1
30IP: 10.244.0.3
31IP: fe80::dc26:15ff:fee1:cbe7
32RemoteAddr: 10.244.0.4:54224
33GET / HTTP/1.1
34Host: foo.example.com
35User-Agent: curl/8.7.1
36Accept: */*
37Accept-Encoding: gzip
38X-Forwarded-For: 10.244.0.1
39X-Forwarded-Host: foo.example.com
40X-Forwarded-Port: 443
41X-Forwarded-Proto: https
42X-Forwarded-Server: traefik-6489fdb447-jgqgx
43X-Real-Ip: 10.244.0.1
Everything is in place for the server certificates. What about the client certificates? Let’s add the TLS option and the middleware annotations to the ingress rule so that the client certificates are requested:
1-> % cat whoami-wildcard-ingress.yaml
2apiVersion: networking.k8s.io/v1
3kind: Ingress
4metadata:
5 name: whoami-wildcard
6 namespace: whoami
7 annotations:
8 traefik.ingress.kubernetes.io/router.tls.options: traefik-request-client-cert@kubernetescrd
9 traefik.ingress.kubernetes.io/router.middlewares: traefik-pass-client-cert@kubernetescrd
10spec:
11 ingressClassName: traefik
12 rules:
13 - host: '*.example.com'
14 http:
15 paths:
16 - path: /
17 pathType: Prefix
18 backend:
19 service:
20 name: whoami
21 port:
22 name: whoamiweb
23 tls:
24 - hosts:
25 - '*.example.com'
26 secretName: server-wildcard-tls-certificate
27-> % k apply -f whoami-wildcard-ingress.yaml
28ingress.networking.k8s.io/whoami-wildcard configured
29-> % curl --cacert ca.crt --cert client.crt --key client.key https://foo.example.com
30Hostname: whoami
31IP: 127.0.0.1
32IP: ::1
33IP: 10.244.0.3
34IP: fe80::dc26:15ff:fee1:cbe7
35RemoteAddr: 10.244.0.4:50252
36GET / HTTP/1.1
37Host: foo.example.com
38User-Agent: curl/8.7.1
39Accept: */*
40Accept-Encoding: gzip
41X-Forwarded-For: 10.244.0.1
42X-Forwarded-Host: foo.example.com
43X-Forwarded-Port: 443
44X-Forwarded-Proto: https
45X-Forwarded-Server: traefik-6489fdb447-jgqgx
46X-Real-Ip: 10.244.0.1
Hmm that’s odd. The request went through but no client certificate header is present. Remember that the TLS option which is currently set only requires the client certificate to be sent in the request, but does not verify it. Let’s check the Traefik logs:
1traefik-6489fdb447-jgqgx 2025-07-06T11:52:27Z DBG github.com/traefik/traefik/v3/pkg/mid
2dlewares/passtlsclientcert/pass_tls_client_cert.go:154 > Tried to extract a certificate
3 on a request without mutual TLS middlewareName=traefik-pass-client-cert@kubernetescrd
4middlewareType=PassClientTLSCert
As it can be seen in the output, mTLS doesn’t seem to be picked up anymore. This is because Traefik cannot handle wildcard subdomains in the ingress routes because it needs an exact match for checking the certificate. There is a GitHub issue that describes this and also a note in the Traefik docs.
To go around this issue, you can either specify a rule for each subdomain in the ingress and it will work as previously. If you have a lot of domains, you can set a default TLS option for Traefik. This will be the fallback behavior in case no route is matched.
Let’s go with the second option and create a default TLS option for Traefik, then send the curl request again:
1-> % cat traefik-default-tlsoption.yaml
2apiVersion: traefik.io/v1alpha1
3kind: TLSOption
4metadata:
5 name: default
6 namespace: traefik
7spec:
8 clientAuth:
9 secretNames:
10 - ca-tls-certificate
11 clientAuthType: RequireAndVerifyClientCert
12-> % k apply -f traefik-default-tlsoption.yaml
13tlsoption.traefik.io/default created
14-> % curl --cacert ca.crt --cert client.crt --key client.key https://foo.example.com
15Hostname: whoami
16IP: 127.0.0.1
17IP: ::1
18IP: 10.244.0.3
19IP: fe80::dc26:15ff:fee1:cbe7
20RemoteAddr: 10.244.0.4:54310
21GET / HTTP/1.1
22Host: foo.example.com
23User-Agent: curl/8.7.1
24Accept: */*
25Accept-Encoding: gzip
26X-Forwarded-For: 10.244.0.1
27X-Forwarded-Host: foo.example.com
28X-Forwarded-Port: 443
29X-Forwarded-Proto: https
30X-Forwarded-Server: traefik-6489fdb447-jgqgx
31X-Forwarded-Tls-Client-Cert: MIICbzCCAhSgAwIBAgIUORIAiefPdDRTDdMUPItO2bmZeLEwCgYIKoZIzj0EAwIwgZIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJjYTESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLmNvbTAeFw0yNTA3MDMxMzE0NTRaFw0zNTA3MDExMzE0NTRaMIGWMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTEPMA0GA1UECwwGY2xpZW50MRIwEAYDVQQDDAlsb2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEWFkbWluQGV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeba9ydqEa9dy4K6rxYHMg/H3OVkWeJ0Df+3IwCUvjlyuDwT1R6XKsqCpS2n0gKM3gQzh+ZBguvYAVMvrS4k/DKNCMEAwHQYDVR0OBBYEFBflZ0zG80LNSFR1bawiQuwtGxc5MB8GA1UdIwQYMBaAFFLGP9dP7t3q8tWEA6Tk8B/tIDxaMAoGCCqGSM49BAMCA0kAMEYCIQCc6BMwzUZCrwJTQ3OZzMdeyp/sRviLXfbfKGd1RFI1PQIhAMhRVPtwVGYqZJXQ4W6BFfB7vkATI9pIyQDLrOc1dCCl
32X-Real-Ip: 10.244.0.1
As you can see, the client certificate was sent this time.
The downside to using a default TLS option is that it will be the fallback option for all the requests in the cluster if Traefik is used as an ingress controller. Not all services in your cluster might require a client certificate to be sent. Make sure to think about your specific use case and choose one of the client authentication types that Traefik provides.