April 2026 • DevOps Engineering • 3 min read

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.

Bash
# 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.

Bash
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:

Bash
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:

Yaml
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.

Bash
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:

Yaml
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:

  1. Distribute tls.crt to the end users on the VPN.
  2. Install on Windows:
    • Double-click tls.crt > Install Certificate.
    • Store Location: Local Machine.
    • Place in: Trusted Root Certification Authorities.

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.yaml on the server node.

Verification Command:

To quickly verify if the correct certificate is being served:

Bash
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