All Products
Search
Document Center

Container Service for Kubernetes:Configure advanced NGINX Ingress features

Last Updated:Jun 18, 2026

Set up URL rewrites, TLS, mTLS, canary releases, and cert-manager for NGINX Ingress.

Prerequisites

Ensure the following:

NGINX configuration methods

The NGINX Ingress controller in ACK is fully compatible with the open source upstream. Three methods are available:

Method Scope Reference
Annotation Individual Ingress — modify a specific Ingress YAML Annotations
ConfigMap All Ingresses — modify the kube-system/nginx-configuration ConfigMap ConfigMaps
Custom NGINX template Full control — customize the NGINX template when other methods are insufficient Custom NGINX template

Configure URL redirection

The NGINX Ingress controller forwards requests using the full path by default. If your backend path differs, use the nginx.ingress.kubernetes.io/rewrite-target annotation to rewrite it.

NGINX Ingress controller 0.22.0 and later require regular expressions with capture groups in the path, used with the rewrite-target annotation.

Apply the template for your cluster's Kubernetes version.

Kubernetes 1.19 and later

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: foo.bar.com
  namespace: default
  annotations:
    # Rewrite the request path: strip the /svc prefix and forward the remainder to the backend.
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /svc(/|$)(.*)
        backend:
          service:
            name: web1-service
            port:
              number: 80
        pathType: ImplementationSpecific
EOF

Kubernetes 1.19 or earlier

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: foo.bar.com
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /svc(/|$)(.*)
        backend:
          serviceName: web1-service
          servicePort: 80
EOF

Verify the configuration:

  1. Get the Ingress IP address.

    kubectl get ingress

    Expected output:

    NAME           CLASS   HOSTS         ADDRESS        PORTS   AGE
    foo.bar.com    nginx   foo.bar.com   172.16.XX.XX   80      46m
  2. Send a request using the Ingress IP address from ADDRESS.

    curl -k -H "Host: foo.bar.com" http://<ADDRESS>/svc/foo

    Expected output:

    web1: /foo

    The /svc prefix is stripped, and the backend receives /foo.

Configure rewrite rules

The nginx.ingress.kubernetes.io/rewrite-target annotation handles basic path rewrites. For advanced rewrites, use snippet annotations to inject custom NGINX configuration directly:

Annotation Target block Use case
nginx.ingress.kubernetes.io/server-snippet server {} block Server-level rewrites, redirects
nginx.ingress.kubernetes.io/configuration-snippet location {} block Location-level rewrites, custom headers

Example:

annotations:
  nginx.ingress.kubernetes.io/server-snippet: |
    rewrite ^/v4/(.*)/card/query http://foo.bar.com/v5/#!/card/query permanent;
  nginx.ingress.kubernetes.io/configuration-snippet: |
    rewrite ^/v6/(.*)/card/query http://foo.bar.com/v7/#!/card/query permanent;

Verify the snippet in the generated nginx.conf:

kubectl exec nginx-ingress-controller-xxxxx --namespace kube-system -- cat /etc/nginx/nginx.conf
# Replace xxxxx with the actual pod name.

The server-snippet appears in the server {} block and the configuration-snippet in the location {} block:

# start server foo.bar.com
server {
    server_name foo.bar.com;
    listen 80;
    listen [::]:80;
    set $proxy_upstream_name "-";
    # server-snippet
    rewrite ^/v4/(.*)/card/query http://foo.bar.com/v5/#!/card/query permanent;
    ...
    # configuration-snippet
    rewrite ^/v6/(.*)/card/query http://foo.bar.com/v7/#!/card/query permanent;
    ...
}
# end server foo.bar.com

Configure global snippets with server-snippet in ConfigMap. See the NGINX rewrite module documentation for full rewrite syntax.

Configure a TLS certificate for Ingress

Attach a TLS certificate to an Ingress to enable HTTPS.

The TLS certificate domain must match the Ingress host value. A mismatch prevents the controller from loading the certificate.

Step 1: Create the certificate and Secret.

  1. Generate a self-signed certificate and private key.

    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
      -keyout tls.key -out tls.crt \
      -subj "/CN=foo.bar.com/O=foo.bar.com"
  2. Store the certificate and key in a Kubernetes Secret.

    kubectl create secret tls tls-test-ingress --key tls.key --cert tls.crt

Step 2: Create an Ingress that references the Secret.

Kubernetes 1.19 and later

cat <<EOF | kubectl create -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-test-ingress
spec:
  tls:
  - hosts:
    - foo.bar.com
    secretName: tls-test-ingress  # Reference the Secret created above.
  rules:
  - host: tls-test-ingress.com
    http:
      paths:
      - path: /foo
        backend:
          service:
            name: web1-svc
            port:
              number: 80
        pathType: ImplementationSpecific
EOF

Kubernetes 1.19 or earlier

cat <<EOF | kubectl create -f -
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-test-ingress
spec:
  tls:
  - hosts:
    - foo.bar.com
    secretName: tls-test-ingress
  rules:
  - host: tls-test-ingress.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: web1-svc
          servicePort: 80
EOF

Update your hosts file or DNS to resolve tls-test-ingress.com to the Ingress IP. Access the service at https://tls-test-ingress.com/foo.

Configure mutual TLS authentication

mTLS requires both server and client to present certificates during the TLS handshake.

mTLS is applied per host — different paths under the same host cannot have separate settings.

Four annotations control mTLS:

Annotation Description
nginx.ingress.kubernetes.io/auth-tls-verify-client Enables client certificate verification. Values: "on" (require valid certificate; HTTP 400 if missing), "optional" (request but do not require), or "off" (disable).
nginx.ingress.kubernetes.io/auth-tls-secret The namespace/secretName of the Secret with the CA certificate for client verification.
nginx.ingress.kubernetes.io/auth-tls-verify-depth The maximum depth of the client certificate chain. Default: 1.
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream If "true", forwards the client certificate to the backend in a request header.

Step 1: Create a self-signed certificate authority (CA).

openssl req -x509 -sha256 -newkey rsa:4096 \
  -keyout ca.key -out ca.crt \
  -days 356 -nodes \
  -subj '/CN=Fern Cert Authority'

Expected output:

Generating a 4096 bit RSA private key
...
writing new private key to 'ca.key'

Step 2: Create a server certificate.

  1. Generate the certificate signing request (CSR).

    openssl req -new -newkey rsa:4096 \
      -keyout server.key -out server.csr \
      -nodes -subj '/CN=foo.bar.com'
  2. Sign the CSR with the CA to produce the server certificate.

    openssl x509 -req -sha256 -days 365 \
      -in server.csr -CA ca.crt -CAkey ca.key \
      -set_serial 01 -out server.crt

    Expected output:

    Signature ok
    subject=/CN=foo.bar.com
    Getting CA Private Key

Step 3: Create a client certificate.

  1. Generate the client CSR.

    openssl req -new -newkey rsa:4096 \
      -keyout client.key -out client.csr \
      -nodes -subj '/CN=Fern'
  2. Sign the client CSR with the CA.

    openssl x509 -req -sha256 -days 365 \
      -in client.csr -CA ca.crt -CAkey ca.key \
      -set_serial 02 -out client.crt

    Expected output:

    Signature ok
    subject=/CN=Fern
    Getting CA Private Key

Step 4: Confirm all certificate files are present.

ls

Expected output:

ca.crt  ca.key  client.crt  client.csr  client.key  server.crt  server.csr  server.key

Step 5: Create Secrets for the CA and server certificates.

kubectl create secret generic ca-secret --from-file=ca.crt=ca.crt
kubectl create secret generic tls-secret --from-file=tls.crt=server.crt --from-file=tls.key=server.key

Step 6: Create the NGINX Ingress with mTLS annotations.

Kubernetes 1.19 and later

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
    nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
    nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
    nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
  name: nginx-test
  namespace: default
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          service:
            name: http-svc
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - foo.bar.com
    secretName: tls-secret
EOF

Kubernetes 1.19 or earlier

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
    nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret"
    nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
    nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
  name: nginx-test
  namespace: default
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: http-svc
          servicePort: 80
        path: /
  tls:
  - hosts:
    - foo.bar.com
    secretName: tls-secret
EOF

Step 7: Create or update the /etc/hosts file.

Get the Ingress IP address and add it to /etc/hosts.

kubectl get ing

Expected output:

NAME         HOSTS         ADDRESS        PORTS     AGE
nginx-test   foo.bar.com   39.102.XX.XX   80, 443   4h42m
echo "39.102.XX.XX  foo.bar.com" | sudo tee -a /etc/hosts

Verify the configuration:

  • Without a client certificate — the server rejects the request:

    curl --cacert ./ca.crt https://foo.bar.com

    Expected output:

    <html>
    <head><title>400 No required SSL certificate was sent</title></head>
    <body>
    <center><h1>400 Bad Request</h1></center>
    <center>No required SSL certificate was sent</center>
    <hr><center>nginx/1.19.0</center>
    </body>
    </html>
  • With a client certificate — the request succeeds:

    curl --cacert ./ca.crt --cert ./client.crt --key ./client.key https://foo.bar.com

    Expected output: the default nginx welcome page (Welcome to nginx!).

Forward HTTPS requests to backend containers

The NGINX Ingress controller forwards HTTP to backends by default. For HTTPS backends, add the nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" annotation.

Kubernetes 1.19 and later

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend-https
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  tls:
  - hosts:
    - <your-host-name>
    secretName: <your-secret-cert-name>
  rules:
  - host: <your-host-name>
    http:
      paths:
      - path: /
        backend:
          service:
            name: <your-service-name>
            port:
              number: <your-service-port>
        pathType: ImplementationSpecific

Kubernetes 1.19 or earlier

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: backend-https
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  tls:
  - hosts:
    - <your-host-name>
    secretName: <your-secret-cert-name>
  rules:
  - host: <your-host-name>
    http:
      paths:
      - path: /
        backend:
          serviceName: <your-service-name>
          servicePort: <your-service-port>

Replace placeholders with your values:

Placeholder Description
<your-host-name> Your domain name
<your-secret-cert-name> The Secret containing your TLS certificate
<your-service-name> Your backend Service name
<your-service-port> Your backend Service port

Use regular expressions for domain names

Kubernetes does not support regular expressions in Ingress host fields by default. Use the nginx.ingress.kubernetes.io/server-alias annotation to add regex-based server aliases to nginx.conf.

Step 1: Create an Ingress with a regex server alias.

This example matches any hostname of the form www.<digits>.example.com and abc.example.com.

Kubernetes 1.19 and later

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-regex
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/server-alias: '~^www\.\d+\.example\.com$, abc.example.com'
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          service:
            name: http-svc1
            port:
              number: 80
        pathType: ImplementationSpecific
EOF

Kubernetes 1.19 or earlier

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-regex
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/server-alias: '~^www\.\d+\.example\.com$, abc.example.com'
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: http-svc1
          servicePort: 80
EOF

Step 2: Verify that the aliases are active in nginx.conf.

  1. Get the NGINX Ingress controller pod name.

    kubectl get pods -n kube-system | grep nginx-ingress-controller

    Expected output:

    nginx-ingress-controller-77cd987c4c-c****   1/1   Running   0   1h
    nginx-ingress-controller-77cd987c4c-x****   1/1   Running   0   1h
  2. Inspect the generated configuration.

    kubectl exec -n kube-system nginx-ingress-controller-77cd987c4c-c**** cat /etc/nginx/nginx.conf | grep -C3 "foo.bar.com"

    Expected output — the server_name directive lists all aliases:

    server {
      server_name foo.bar.com abc.example.com ~^www\.\d+\.example\.com$ ;
      listen 80  ;
      listen 443  ssl http2 ;

Step 3: Get the Ingress IP address.

kubectl get ing

Expected output:

NAME            HOSTS         ADDRESS        PORTS   AGE
ingress-regex   foo.bar.com   101.37.XX.XX   80      11s

Step 4: Test all matching hostnames.

Replace <IP_ADDRESS> with the address from the previous step.

curl -H "Host: foo.bar.com" <IP_ADDRESS>/foo        # /foo
curl -H "Host: www.123.example.com" <IP_ADDRESS>/foo # /foo
curl -H "Host: www.321.example.com" <IP_ADDRESS>/foo # /foo

Specify wildcard domain names

NGINX Ingresses natively support wildcard domain names. This example matches any subdomain of ingress-regex.com.

Step 1: Create an Ingress with a wildcard host.

Kubernetes 1.19 and later

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-regex
  namespace: default
spec:
  rules:
  - host: "*.ingress-regex.com"
    http:
      paths:
      - path: /foo
        backend:
          service:
            name: http-svc1
            port:
              number: 80
        pathType: ImplementationSpecific
EOF

Kubernetes 1.19 or earlier

cat <<-EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-regex
  namespace: default
spec:
  rules:
  - host: "*.ingress-regex.com"
    http:
      paths:
      - path: /foo
        backend:
          serviceName: http-svc1
          servicePort: 80
EOF

Step 2: Verify the server_name in nginx.conf.

kubectl exec -n kube-system <nginx-ingress-pod-name> cat /etc/nginx/nginx.conf | grep -C3 "ingress-regex.com"

Replace <nginx-ingress-pod-name> with the actual pod name.

Expected output (older controller versions):

# start server *.ingress-regex.com
server {
  server_name *.ingress-regex.com ;
  listen 80;
  listen [::]:80;
...
}
# end server *.ingress-regex.com

Expected output (latest controller versions):

## start server *.ingress-regex.com
server {
  server_name ~^(?<subdomain>[\w-]+)\.ingress-regex\.com$ ;
  listen 80;
  listen [::]:80;
...
}
## end server *.ingress-regex.com

Step 3: Get the Ingress IP address.

kubectl get ing

Expected output:

NAME            HOSTS                 ADDRESS        PORTS   AGE
ingress-regex   *.ingress-regex.com   101.37.XX.XX   80      11s

Step 4: Test wildcard matching.

Replace <IP_ADDRESS> with the address from the previous step.

curl -H "Host: abc.ingress-regex.com" <IP_ADDRESS>/foo   # /foo
curl -H "Host: 123.ingress-regex.com" <IP_ADDRESS>/foo   # /foo
curl -H "Host: a1b1.ingress-regex.com" <IP_ADDRESS>/foo  # /foo

Use annotations for canary releases

Canary releases route a subset of traffic to a new service version. All canary configurations require nginx.ingress.kubernetes.io/canary: "true" on the canary Ingress.

These annotations control traffic splitting, evaluated in order: header-based → cookie-based → weight-based.

Annotation Values Behavior when condition is not met
nginx.ingress.kubernetes.io/canary-weight Integer 0–100 (percentage) N/A — acts as the final fallback
nginx.ingress.kubernetes.io/canary-by-header Any header name If the header is absent or matches neither always nor never, falls through to cookie-based or weight-based rules
nginx.ingress.kubernetes.io/canary-by-header-value Custom string (used with canary-by-header) If the header value does not match, falls through to the next rule by precedence
nginx.ingress.kubernetes.io/canary-by-cookie Cookie name Cookie value must be always or never — no custom values supported

Weight-based canary release

Routes 20% of traffic to the canary Service:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "20"

Header-based canary release

If header ack is always, traffic routes to the canary. If never, the canary is skipped. Other values fall back to weight (50%):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "50"
    nginx.ingress.kubernetes.io/canary-by-header: "ack"

Header-based canary release with a custom header value

If header ack equals alibaba, traffic routes to the canary. Other values fall back to weight (20%):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "20"
    nginx.ingress.kubernetes.io/canary-by-header: "ack"
    nginx.ingress.kubernetes.io/canary-by-header-value: "alibaba"

Cookie-based canary release

If no header rule matches and cookie hangzhou_region is always, traffic routes to the canary. Other cases fall back to weight (20%):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "20"
    nginx.ingress.kubernetes.io/canary-by-header: "ack"
    nginx.ingress.kubernetes.io/canary-by-header-value: "alibaba"
    nginx.ingress.kubernetes.io/canary-by-cookie: "hangzhou_region"
Cookie values must be always or never. Custom values are not supported.

For a full walkthrough, see Use the NGINX Ingress controller to implement canary releases and blue-green deployments.

Obtain a free TLS certificate with cert-manager

cert-manager automates TLS certificate provisioning and renewal through Let's Encrypt in Kubernetes.

This YAML is intended for ASK (Serverless Kubernetes) clusters. For ACK clusters, see the cert-manager installation guide.

Deploy cert-manager

kubectl apply -f https://raw.githubusercontent.com/AliyunContainerService/serverless-k8s-examples/master/cert-manager/ask-cert-manager.yaml

Verify that cert-manager pods are running

kubectl get pods -n cert-manager

Expected output:

NAME                     READY   STATUS    RESTARTS   AGE
cert-manager-1           1/1     Running   0          2m11s
cert-manager-cainjector  1/1     Running   0          2m11s
cert-manager-webhook     1/1     Running   0          2m10s

Create a ClusterIssuer

cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-http01
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: <your_email_name@gmail.com>  # Replace with your email address.
    privateKeySecretRef:
      name: letsencrypt-http01
    solvers:
    - http01:
        ingress:
          class: nginx
EOF

Confirm the ClusterIssuer is ready

kubectl get clusterissuer

Expected output:

NAME                         READY   AGE
letsencrypt-prod-http01      True    17s

Create an Ingress that requests a certificate

The domain name must meet these conditions:

  • No more than 64 characters

  • Not a wildcard domain name

  • Publicly accessible over HTTP (required for the ACME HTTP-01 challenge)

Kubernetes 1.19 and later

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-tls
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod-http01"
spec:
  tls:
  - hosts:
    - <your_domain_name>       # Replace with your domain name.
    secretName: ingress-tls
  rules:
  - host: <your_domain_name>   # Replace with your domain name.
    http:
      paths:
      - path: /
        backend:
          service:
            name: <your_service_name>   # Replace with your Service name.
            port:
              number: <your_service_port>  # Replace with your Service port.
        pathType: ImplementationSpecific
EOF

Kubernetes 1.19 or earlier

cat <<EOF | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tls
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod-http01"
spec:
  tls:
  - hosts:
    - <your_domain_name>
    secretName: ingress-tls
  rules:
  - host: <your_domain_name>
    http:
      paths:
      - path: /
        backend:
          serviceName: <your_service_name>
          servicePort: <your_service_port>
EOF

Monitor certificate issuance

kubectl get cert

Expected output (issuance may take a few minutes):

NAME          READY   SECRET        AGE
ingress-tls   True    ingress-tls   52m

If READY is not True, inspect the certificate for details:

kubectl describe cert ingress-tls

Look for an Events section like:

Events:
  Type    Reason     Age    From          Message
  ----    ------     ---    ----          -------
  Normal  Requested  64s    cert-manager  Created new CertificateRequest resource "ingress-tls-xxxxx"
  Normal  Issuing    40s    cert-manager  The certificate has been successfully issued

Verify the certificate Secret

kubectl get secret ingress-tls

Expected output:

NAME          TYPE                DATA   AGE
ingress-tls   kubernetes.io/tls   2      2m

Access https://<your_domain_name> to confirm HTTPS is active.

Next steps