Technical Guide: Securing Kubernetes Ingress via Static IP (RKE2/Nginx)
Overview
When accessing Kubernetes services via an IP address (e.g., https://192.168.5.x) over a VPN, browsers fail to validate the connection for two reasons:
- Host-less Requests: Browsers do not send an SNI (Server Name Indication) when hitting an IP, causing Nginx to serve its "Fake Ingress Certificate."
- Missing SANs: Modern security standards require the IP to be present in the Subject Alternative Name (SAN) field of the certificate.
The Solution: We generate a custom self-signed certificate with an IP-SAN and configure the Nginx Ingress Controller to use it as the Global Default/Fallback certificate.
Phase 1: Generate the IP-Specific Certificate
Generate the certificate on a Linux machine with OpenSSL. You must include the IP in the SAN.
# 1. Create a directory for security
mkdir ingress-certs && cd ingress-certs
# 2. Generate the Private Key and Certificate
# Replace 192.168.5.x with your actual target IP
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout tls.key \
-out tls.crt \
-subj "/CN=192.168.5.x" \
-addext "subjectAltName=IP:192.168.5.x"
Phase 2: Create the Kubernetes Secret
The secret must reside in the same namespace as the Ingress Controller pods. For RKE2, this is always kube-system.
kubectl create secret tls ingress-fallback-cert \
--cert=tls.crt \
--key=tls.key \
-n kube-system
Phase 3: Configure Nginx Fallback (RKE2)
Since RKE2 deploys Nginx as a DaemonSet, we must modify the controller arguments to point to our new secret.
Edit the DaemonSet:
kubectl edit ds rke2-ingress-nginx-controller -n kube-system
Add the Fallback Argument:
Locate the spec.template.spec.containers[0].args section and add the --default-ssl-certificate flag:
containers:
- args:
- /nginx-ingress-controller
- --default-ssl-certificate=kube-system/ingress-fallback-cert # <--- ADD THIS
- --election-id=rke2-ingress-nginx-leader
- --ingress-class=nginx
# ... existing args ...
Verify Rollout:
Kubernetes will perform a rolling restart of the Ingress pods.
kubectl get pods -n kube-system -l app.kubernetes.io/name=rke2-ingress-nginx -w
Phase 4: Configure the Ingress Resource
In your Helm chart or YAML manifests, ensure your Ingress is a "catch-all" (no specific host) so it captures IP-based traffic.
Values.yaml snippet:
ingress:
enabled: true
className: nginx
hosts:
- host: "" # Leaving this empty targets the IP directly
paths:
- path: /
pathType: Prefix
tls: [] # Leave empty; Nginx will use the global fallback secret
Phase 5: Client-Side Trust (The "Green Lock")
Even with a valid IP-SAN, the certificate is self-signed. To remove the browser "Not Secure" warning:
- Distribute
tls.crtto the end users on the VPN. - Install on Windows:
- Double-click
tls.crt> Install Certificate. - Store Location: Local Machine.
- Place in: Trusted Root Certification Authorities.
- Double-click
Result: Browsers (Chrome/Edge) will now show a Green Lock for https://192.168.5.x/.
Best Practices & Maintenance
- Certificate Expiry: The command above sets a 10-year (3650 days) expiry. Set a reminder to rotate the secret before it expires.
- RKE2 Upgrades: RKE2 manages its Ingress via Helm. A major upgrade might reset the DaemonSet arguments.
- Advanced Tip: To make this change permanent, create a HelmChartConfig file at
/var/lib/rancher/rke2/server/manifests/rke2-ingress-nginx-config.yamlon the server node.
- Advanced Tip: To make this change permanent, create a HelmChartConfig file at
Verification Command:
To quickly verify if the correct certificate is being served:
curl -v -k https://<YOUR_IP>/ 2>&1 | grep -i "subject"
Ref Reference ID: K8S-SSL-IP-SAN-2024
Environment: RKE2 / Nginx-Ingress
Author: DevOps Engineering