Set up URL rewrites, TLS, mTLS, canary releases, and cert-manager for NGINX Ingress.
Prerequisites
Ensure the following:
-
An ACK managed cluster is created.
-
The NGINX Ingress controller is installed and running.
-
kubectl is connected to the cluster.
-
A Deployment and a Service are deployed in the cluster.
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:
-
Get the Ingress IP address.
kubectl get ingressExpected output:
NAME CLASS HOSTS ADDRESS PORTS AGE foo.bar.com nginx foo.bar.com 172.16.XX.XX 80 46m -
Send a request using the Ingress IP address from
ADDRESS.curl -k -H "Host: foo.bar.com" http://<ADDRESS>/svc/fooExpected output:
web1: /fooThe
/svcprefix 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.
-
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" -
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.
-
Generate the certificate signing request (CSR).
openssl req -new -newkey rsa:4096 \ -keyout server.key -out server.csr \ -nodes -subj '/CN=foo.bar.com' -
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.crtExpected output:
Signature ok subject=/CN=foo.bar.com Getting CA Private Key
Step 3: Create a client certificate.
-
Generate the client CSR.
openssl req -new -newkey rsa:4096 \ -keyout client.key -out client.csr \ -nodes -subj '/CN=Fern' -
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.crtExpected 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.comExpected 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.comExpected 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.
-
Get the NGINX Ingress controller pod name.
kubectl get pods -n kube-system | grep nginx-ingress-controllerExpected output:
nginx-ingress-controller-77cd987c4c-c**** 1/1 Running 0 1h nginx-ingress-controller-77cd987c4c-x**** 1/1 Running 0 1h -
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_namedirective 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 bealwaysornever. 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.