NGINX Ingress is a Kubernetes API object that provides Layer 7 load balancing for managing external access to Services in a cluster. This topic covers advanced NGINX Ingress configurations: URL redirection, rewrite rules, TLS termination, mutual TLS authentication (mTLS), HTTPS backend forwarding, regular expression and wildcard domain names, canary releases, and free TLS certificates via cert-manager.
Prerequisites
Before you begin, ensure that you have:
-
An ACK cluster. See Create an ACK managed cluster.
-
The NGINX Ingress controller installed and running. See Manage the NGINX Ingress controller.
-
kubectl connected to the cluster. See Obtain cluster credentials and connect with kubectl.
-
A Deployment and a Service deployed in the cluster. See Create an NGINX Ingress by using kubectl.
NGINX configuration methods
The NGINX Ingress controller in ACK is fully compatible with the upstream open source component. Three configuration methods are available:
| Method | Scope | Reference |
|---|---|---|
| Annotation | Individual Ingress — modify the YAML template of a specific Ingress | Annotations |
| ConfigMap | All Ingresses — modify the kube-system/nginx-configuration ConfigMap |
ConfigMaps |
| Custom NGINX template | Full control — customize the NGINX template when the above methods are insufficient | Custom NGINX template |
Configure URL redirection
By default, the NGINX Ingress controller forwards requests using the full request path. If the path of your backend service differs from the incoming request path, use the nginx.ingress.kubernetes.io/rewrite-target annotation to rewrite the path.
NGINX Ingress controller version 0.22.0 and later require regular expressions with capture groups in the path, used together with the rewrite-target annotation.
Apply the following template based on 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;
To verify that the snippet is active, inspect 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 configuration appears in the server {} block and the configuration-snippet configuration appears 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
For global snippet configuration options, see server-snippet in ConfigMap. For the full rewrite directive syntax, see the NGINX rewrite module documentation.
Configure a TLS certificate for Ingress
Attach a TLS certificate to an Ingress to enable HTTPS for your service.
The domain name in the TLS certificate must match the host value in the Ingress rules. A mismatch prevents the NGINX Ingress 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
After the Ingress is created, update your hosts file or DNS so that tls-test-ingress.com resolves to the Ingress IP address. Access the service at https://tls-test-ingress.com/foo.
Configure mutual TLS authentication
Mutual TLS authentication (mTLS) requires both the server and the client to present certificates during the TLS handshake.
mTLS is applied per host. You cannot configure different mTLS settings for individual paths under the same host.
The four annotations that control mTLS are:
| Annotation | Description |
|---|---|
nginx.ingress.kubernetes.io/auth-tls-verify-client |
Enables client certificate verification. Accepted values: "on" (require a valid certificate — returns HTTP 400 if missing), "optional" (request but do not require a certificate), or "off" (disable verification). |
nginx.ingress.kubernetes.io/auth-tls-secret |
The namespace/secretName of the Secret containing the CA certificate used to verify client certificates. |
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 |
When set to "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: Update your `/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 4h42mecho "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
By default, the NGINX Ingress controller forwards HTTP to backend containers. If your backend uses HTTPS, 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 the placeholders with your actual values:
| Placeholder | Description |
|---|---|
<your-host-name> |
Your domain name |
<your-secret-cert-name> |
The Secret containing your TLS certificate |
<your-service-name> |
The name of your backend Service |
<your-service-port> |
The port your backend Service listens on |
Use regular expressions for domain names
By default, Kubernetes does not support regular expressions in Ingress host fields. The nginx.ingress.kubernetes.io/server-alias annotation adds server aliases to the generated nginx.conf, enabling regular expression matching.
Step 1: Create an Ingress with a regex server alias.
The following example matches any hostname of the form www.<digits>.example.com in addition to 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. The following 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 to perform canary releases
Canary releases route a subset of traffic to a new version of your service. All canary configurations require nginx.ingress.kubernetes.io/canary: "true" on the canary Ingress.
The following annotations control how traffic is split. Rules are evaluated in this 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
When the request header ack is always, routes to the canary. When it is never, skips the canary. For any other value, falls back to the 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
When the header ack equals alibaba, routes to the canary. For any other value, falls back to the 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
When no header rule matches and the cookie hangzhou_region is always, routes to the canary. Falls back to the weight (20%) for all other cases:
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"
The cookie value must bealwaysornever. Custom cookie values are not supported.
For a complete canary release walkthrough, see Use the NGINX Ingress controller to implement canary releases and blue-green deployments.
Use cert-manager to apply for a free TLS certificate
cert-manager is an open source tool for managing cloud-native certificates in Kubernetes. It requests TLS certificates from Let's Encrypt and handles auto-renewal automatically.
The deployment YAML in this section is configured for ACK clusters. For general Kubernetes clusters, follow the upstream cert-manager installation guide.
Step 1: Deploy cert-manager
kubectl apply -f https://raw.githubusercontent.com/AliyunContainerService/serverless-k8s-examples/master/cert-manager/ask-cert-manager.yaml
Step 2: 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
Step 3: 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
Step 4: Confirm the ClusterIssuer is ready
kubectl get clusterissuer
Expected output:
NAME READY AGE
letsencrypt-prod-http01 True 17s
Step 5: Create an Ingress that requests a certificate
The domain name must meet all of the following 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
Step 6: 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, check the certificate status for details:
kubectl describe cert ingress-tls
Look for an Events section similar to:
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
Step 7: Verify the certificate Secret
kubectl get secret ingress-tls
Expected output:
NAME TYPE DATA AGE
ingress-tls kubernetes.io/tls 2 2m
Access your service at https://<your_domain_name> to confirm that HTTPS is active.