Cart

    Sorry, we could not find any results for your search querry.

    Configuring mTLS between pods in Kubernetes

    Traffic between pods in a standard Kubernetes cluster is not encrypted automatically. To require services to communicate with each other exclusively through TLS or mTLS, configure this yourself or use a service mesh.

    In this guide, you configure application-level mTLS without an additional service mesh, such as Linkerd. You create your own Certificate Authority (CA), server and client certificates, expose an internal service through Nginx with mandatory client authentication, and then test both successful and rejected connections.

    • Use this approach to enforce mTLS for a specific application or internal API.
       
    • To enforce mTLS centrally across the cluster, use a service mesh such as Linkerd or Istio.
       
    • Never store private keys in Git. Store them in Secrets or an external secret manager.
       
    • For this guide, you need kubectl.
     

     

    When do you use mTLS?

     

    • Use this: for internal APIs, compliance requirements, or services that must not accept unsecured pod-to-pod traffic.
    • Do not use this as the only measure: always combine mTLS with namespaces, NetworkPolicies, and least privilege.
    • Use a service mesh: if you do not want to manage certificates and Nginx configurations for each application.

    The setup in this guide explicitly encrypts and authenticates traffic between pods. This prevents internal services from accepting unsecured connections.


     

    Create the TLS certificates

     

    Step 1

     and create a local working directory for the certificates:

    mkdir -p ~/kb-mtls
    cd ~/kb-mtls

     

    Step 2

    Create a CA certificate that will sign the server and client certificates:

    openssl req -x509 -newkey rsa:2048 -nodes \
      -keyout ca.key \
      -out ca.crt \
      -subj "/CN=kb-mtls-ca" \
      -days 365

    The `-nodes` flag prevents OpenSSL from prompting for a passphrase. This is practical for test environments, but use additional protection for private keys in production.


     

    Step 3

    Next, create a server certificate for the internal service:

    openssl req -newkey rsa:2048 -nodes \
      -keyout server.key \
      -out server.csr \
      -subj "/CN=mtls-server.kb-mtls-test.svc.cluster.local"

    Then create an extension file containing the DNS names of your service:

    nano server.ext

    Add the following content to the file:

    subjectAltName=DNS:mtls-server,DNS:mtls-server.kb-mtls-test.svc,DNS:mtls-server.kb-mtls-test.svc.cluster.local

    Save the changes and close the file (ctrl + x > y > enter).

    Then sign the server certificate:

    openssl x509 -req \
      -in server.csr \
      -CA ca.crt \
      -CAkey ca.key \
      -CAcreateserial \
      -out server.crt \
      -days 365 \
      -sha256 \
      -extfile server.ext

     

    Step 4

    Also create a client certificate for the pod that is allowed to communicate with the service:

    openssl req -newkey rsa:2048 -nodes \
      -keyout client.key \
      -out client.csr \
      -subj "/CN=mtls-client"
    
    openssl x509 -req \
      -in client.csr \
      -CA ca.crt \
      -CAkey ca.key \
      -CAcreateserial \
      -out client.crt \
      -days 365 \
      -sha256

     

    Create the Secrets and workloads

     

    Step 1

    First, create a namespace and two Secrets: one for the server and one for the client:

    kubectl create namespace kb-mtls-test
    
    kubectl -n kb-mtls-test create secret generic mtls-server \
      --from-file=ca.crt=ca.crt \
      --from-file=server.crt=server.crt \
      --from-file=server.key=server.key
    
    kubectl -n kb-mtls-test create secret generic mtls-client \
      --from-file=ca.crt=ca.crt \
      --from-file=client.crt=client.crt \
      --from-file=client.key=client.key

    The server uses `ca.crt` to verify client certificates. The client uses its own certificate and private key to authenticate with the server.


     

    Step 2

    Next, create a manifest for the server, service, and two client pods:

    nano mtls-demo.yaml

    Add the following configuration to the file:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: nginx-mtls
      namespace: kb-mtls-test
    data:
      default.conf: |
        server {
          listen 8443 ssl;
          server_name mtls-demo;
    
          ssl_certificate /etc/nginx/tls/server.crt;
          ssl_certificate_key /etc/nginx/tls/server.key;
          ssl_client_certificate /etc/nginx/tls/ca.crt;
          ssl_verify_client on;
    
          location / {
            default_type text/plain;
            return 200 "mtls-ok\n";
          }
        }
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mtls-server
      namespace: kb-mtls-test
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mtls-server
      template:
        metadata:
          labels:
            app: mtls-server
        spec:
          containers:
            - name: nginx
              image: nginx:1.27-alpine
              ports:
                - containerPort: 8443
              volumeMounts:
                - name: config
                  mountPath: /etc/nginx/conf.d/default.conf
                  subPath: default.conf
                - name: tls
                  mountPath: /etc/nginx/tls
                  readOnly: true
          volumes:
            - name: config
              configMap:
                name: nginx-mtls
            - name: tls
              secret:
                secretName: mtls-server
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: mtls-server
      namespace: kb-mtls-test
    spec:
      selector:
        app: mtls-server
      ports:
        - port: 8443
          targetPort: 8443
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: mtls-client
      namespace: kb-mtls-test
    spec:
      restartPolicy: Never
      containers:
        - name: curl
          image: curlimages/curl:8.12.1
          command: ["sh", "-c", "sleep 3600"]
          volumeMounts:
            - name: client-tls
              mountPath: /etc/mtls/client
              readOnly: true
      volumes:
        - name: client-tls
          secret:
            secretName: mtls-client
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: mtls-client-no-cert
      namespace: kb-mtls-test
    spec:
      restartPolicy: Never
      containers:
        - name: curl
          image: curlimages/curl:8.12.1
          command: ["sh", "-c", "sleep 3600"]

    Save the changes and close the file (ctrl + x > y > enter).


     

    Step 3

    Create the resources:

    kubectl apply -f mtls-demo.yaml
    kubectl -n kb-mtls-test rollout status deploy/mtls-server
    kubectl -n kb-mtls-test wait --for=condition=Ready pod/mtls-client --timeout=180s
    kubectl -n kb-mtls-test wait --for=condition=Ready pod/mtls-client-no-cert --timeout=180s

     

    Test successful and rejected connections

     

    Step 1

    Test the connection from the client pod with a client certificate:

    kubectl -n kb-mtls-test exec mtls-client -- sh -c \
      "curl --cacert /etc/mtls/client/ca.crt \
            --cert /etc/mtls/client/client.crt \
            --key /etc/mtls/client/client.key \
            https://mtls-server:8443"

    If everything is configured correctly, the response is `mtls-ok`.


     

    Step 2

    Then test a connection without a client certificate:

    kubectl -n kb-mtls-test exec mtls-client -- sh -c \
      "curl -k -sv https://mtls-server:8443 2>&1 | tail -n 20"

    You should see an error such as `400 No required SSL certificate was sent`. This confirms that the service enforces mTLS for internal traffic.


     

     

    Need help?

    Receive personal support from our supporters

    Contact us