Connect F5 WAF for NGINX to NGINX One Console, NGINX Instance Manager, or HTTPS bundle sources
This document explains how to configure NGINX Ingress Controller to fetch pre-compiled F5 WAF for NGINX policy bundles from a remote source, instead of manually placing bundles on disk.
You can fetch bundles from:
- NGINX One Console — for policies compiled and managed through NGINX One Console
- NGINX Instance Manager — for policies compiled and managed through NGINX Instance Manager
- HTTPS — for compiled
.tgzbundles hosted on any HTTPS server
Bundle sources require F5 WAF for NGINX v5 and work with VirtualServer custom resources only. The deprecatedsecurityLog(singular) field does not support bundle sources — usesecurityLogsinstead.
There are complete NGINX Ingress Controller with F5 WAF for NGINX bundle source example resources on GitHub.
- NGINX Ingress Controller deployed with F5 WAF for NGINX v5. You can also install with Helm.
- An NGINX One Console account with a published WAF policy. See Manage policies.
- A VirtualServer resource to attach the WAF policy to.
ImportantNGINX Ingress Controller does not trigger compilation. Compilation happens when a policy is published in NGINX One Console. Ensure the policy has been published and a compiled bundle is available before continuing.
NGINX One Console uses APIToken authentication. Create a Secret of type nginx.com/waf-bundle with a token key containing your API token.
See the example Secret manifests in the NGINX Ingress Controller repository for the required format.
Create a Policy resource using apBundleSource with type: N1C:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: N1C
url: "https://<tenant>.console.ves.volterra.io"
policyName: "my-blocking-policy"
policyNamespace: "default"
secret: "n1c-credentials"
enablePolling: true
pollInterval: "5m"
EOFReplace <tenant> with your NGINX One Console tenant hostname, policyName with the name of your published policy, and policyNamespace with the NGINX One Console namespace where the policy resides.
CautionTo skip TLS verification for testing, addinsecureSkipVerify: trueto the bundle source. Do not use this in production.
Reference the WAF Policy in your VirtualServer:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: webapp
spec:
host: webapp.example.com
policies:
- name: waf-policy
upstreams:
- name: webapp
service: webapp-svc
port: 80
routes:
- path: /
action:
pass: webapp
EOF-
Check the Policy events for a successful fetch:
kubectl describe policy waf-policyLook for a
Normalevent confirming the bundle was fetched. If you see aWarningevent, check the message for the cause — common issues include an incorrectpolicyName, an invalid token, or a policy that has not been published yet. -
Send a legitimate request to confirm traffic flows normally:
shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ http://webapp.example.com:$IC_HTTP_PORT/ -
Send a malicious request to confirm WAF is blocking:
shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ "http://webapp.example.com:$IC_HTTP_PORT/<script>"Expected response:
<html><head><title>Request Rejected</title></head><body>
If the VirtualServer returns HTTP 500, the bundle has not been fetched yet. Check the Policy events and status for errors.
When enablePolling: true is set, NGINX Ingress Controller periodically checks whether a new bundle is available. For NGINX One Console, it uses a compile status hash — the full bundle is only downloaded when a new compilation is detected.
Check that polls are running without error:
kubectl describe policy waf-policyLook for recent Normal events that confirm a poll completed. A Warning event means the last poll failed, but the existing bundle remains active — WAF protection is not interrupted.
To adjust the poll interval on an existing Policy:
kubectl patch policy waf-policy --type merge -p '{
"spec": {"waf": {"apBundleSource": {"pollInterval": "10m"}}}
}'pollInterval must be at least 1m. It defaults to 5m if not set.
Security log profile bundles can also be fetched from NGINX One Console using apLogBundleSource in securityLogs[]:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: N1C
url: "https://<tenant>.console.ves.volterra.io"
policyName: "my-blocking-policy"
policyNamespace: "default"
secret: "n1c-credentials"
enablePolling: true
pollInterval: "5m"
securityLogs:
- enable: true
apLogBundleSource:
type: N1C
url: "https://<tenant>.console.ves.volterra.io"
policyName: "secops_dashboard"
policyNamespace: "default"
secret: "n1c-credentials"
enablePolling: true
pollInterval: "5m"
logDest: "syslog:server=syslog-svc.default:514"
EOFVerify log events are arriving at your syslog destination:
kubectl exec -it <SYSLOG_POD> -- cat /var/log/messages- NGINX Ingress Controller deployed with F5 WAF for NGINX v5. You can also install with Helm.
- A working NGINX Instance Manager instance with a compiled policy bundle. See Create a security policy bundle.
- A VirtualServer resource to attach the WAF policy to.
ImportantNGINX Ingress Controller does not trigger compilation. Compile the policy using the NGINX Instance Manager UI orPOST /api/platform/v1/security/policies/bundlesand verify compilation succeeded before continuing.
NGINX Instance Manager requires a Secret of type nginx.com/waf-bundle. The Secret must contain either a token key (bearer auth) or username + password keys (basic auth).
See the example Secret manifests in the NGINX Ingress Controller repository for the required format.
Create a Policy resource using apBundleSource with type: NIM:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: NIM
url: "https://nim.example.com"
policyName: "my-blocking-policy"
secret: "nim-credentials"
enablePolling: true
pollInterval: "5m"
EOFReplace url with your NGINX Instance Manager base URL and policyName with the name of your compiled policy.
CautionTo skip TLS verification for testing, addinsecureSkipVerify: trueto the bundle source. Do not use this in production.
Reference the WAF Policy in your VirtualServer:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: webapp
spec:
host: webapp.example.com
policies:
- name: waf-policy
upstreams:
- name: webapp
service: webapp-svc
port: 80
routes:
- path: /
action:
pass: webapp
EOF-
Check the Policy events for a successful fetch:
kubectl describe policy waf-policyLook for a
Normalevent confirming the bundle was fetched. If you see aWarningevent, check the message for the cause — common issues include an incorrectpolicyName, authentication failure, or a bundle that has not been compiled yet. -
Send a legitimate request to confirm traffic flows normally:
shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ http://webapp.example.com:$IC_HTTP_PORT/ -
Send a malicious request to confirm WAF is blocking:
shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ "http://webapp.example.com:$IC_HTTP_PORT/<script>"Expected response:
<html><head><title>Request Rejected</title></head><body>
If the VirtualServer returns HTTP 500, the bundle has not been fetched yet. Check the Policy events and status for errors.
When enablePolling: true is set, NGINX Ingress Controller periodically checks whether a new bundle is available. For NGINX Instance Manager, it uses a metadata hash comparison — the full bundle is only downloaded when the hash has changed.
Check that polls are running without error:
kubectl describe policy waf-policyLook for recent Normal events that confirm a poll completed. A Warning event means the last poll failed, but the existing bundle remains active — WAF protection is not interrupted.
To adjust the poll interval on an existing Policy:
kubectl patch policy waf-policy --type merge -p '{
"spec": {"waf": {"apBundleSource": {"pollInterval": "10m"}}}
}'pollInterval must be at least 1m. It defaults to 5m if not set.
Security log profile bundles can also be fetched from NGINX Instance Manager using apLogBundleSource in securityLogs[]:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: NIM
url: "https://nim.example.com"
policyName: "my-blocking-policy"
secret: "nim-credentials"
enablePolling: true
pollInterval: "5m"
securityLogs:
- enable: true
apLogBundleSource:
type: NIM
url: "https://nim.example.com"
policyName: "secops_dashboard"
secret: "nim-credentials"
enablePolling: true
pollInterval: "5m"
logDest: "syslog:server=syslog-svc.default:514"
EOFVerify log events are arriving at your syslog destination:
kubectl exec -it <SYSLOG_POD> -- cat /var/log/messages- NGINX Ingress Controller deployed with F5 WAF for NGINX v5. You can also install with Helm.
- A compiled
.tgzpolicy bundle hosted on an HTTPS server. To compile a policy bundle, see Compile F5 WAF for NGINX policies. - A VirtualServer resource to attach the WAF policy to.
The url field must point directly to the compiled .tgz bundle file — for example, https://bundles.example.com/waf/my-policy.tgz. NGINX Ingress Controller downloads the file at this URL and does not follow redirects (3xx responses are treated as errors for SSRF protection).
You can host bundles on any HTTPS-capable server:
- In-cluster bundle server — Deploy an NGINX-based server inside your cluster that serves compiled bundles over HTTPS. For an example deployment, see the bundle server manifest in the NGINX Ingress Controller repository.
- Object storage — Use S3, GCS, Azure Blob Storage, or another object store with HTTPS access.
- Artifact registry — Serve bundles from a CI/CD artifact repository or container registry with download URLs.
- Static file server — Any HTTPS server (NGINX, Apache, Caddy) that can serve
.tgzfiles.
After compiling your policy with the F5 WAF compiler, upload the .tgz file to your server and note the full URL.
Skip this step if your HTTPS server uses a publicly trusted certificate.
- Custom CA certificate — If your server uses a self-signed or internal CA, create a Secret of type
nginx.org/cawith aca.crtkey, and reference it intrustedCertSecret. - Client mTLS — Create a
kubernetes.io/tlsSecret withtls.crtandtls.key, and reference it insecret.
See the example Secret manifests in the NGINX Ingress Controller repository for the required format.
Create a Policy resource using apBundleSource with type: HTTPS. The url must be the full path to the .tgz bundle file:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: HTTPS
url: "https://bundles.example.com/waf/my-policy.tgz"
trustedCertSecret: "bundle-ca-cert"
enablePolling: false
EOFReplace url with the full URL of your compiled bundle. Remove trustedCertSecret if your server uses a publicly trusted certificate.
CautionTo skip TLS verification for testing, addinsecureSkipVerify: trueto the bundle source. Do not use this in production.
Reference the WAF Policy in your VirtualServer:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: webapp
spec:
host: webapp.example.com
policies:
- name: waf-policy
upstreams:
- name: webapp
service: webapp-svc
port: 80
routes:
- path: /
action:
pass: webapp
EOF-
Check the Policy events for a successful fetch:
kubectl describe policy waf-policyLook for a
Normalevent confirming the bundle was fetched. If you see aWarningevent, check the message for the cause — common issues include an unreachable URL or a TLS certificate error. -
Send a legitimate request to confirm traffic flows normally:
shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ http://webapp.example.com:$IC_HTTP_PORT/ -
Send a malicious request to confirm WAF is blocking:
shell curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP \ "http://webapp.example.com:$IC_HTTP_PORT/<script>"Expected response:
<html><head><title>Request Rejected</title></head><body>
If the VirtualServer returns HTTP 500, the bundle has not been fetched yet. Check the Policy events and status for errors.
Polling lets NGINX Ingress Controller detect and deploy updated bundles without modifying the Policy resource. For HTTPS sources, NGINX Ingress Controller uses ETag and If-Modified-Since headers — a 304 Not Modified response skips the download entirely.
Enable polling on the existing Policy:
kubectl patch policy waf-policy --type merge -p '{
"spec": {"waf": {"apBundleSource": {"enablePolling": true, "pollInterval": "10m"}}}
}'pollInterval must be at least 1m. It defaults to 5m if not set.
Set verifyChecksum: true to have NGINX Ingress Controller fetch a companion <url>.sha256 file and compare the SHA-256 digest against the downloaded bundle. The bundle is rejected if the digest does not match.
-
Generate the checksum file alongside your bundle:
sha256sum my-policy.tgz > my-policy.tgz.sha256 -
Upload both files to the same location on your HTTPS server.
-
Update the Policy to enable verification:
shell kubectl patch policy waf-policy --type merge -p '{ "spec": {"waf": {"apBundleSource": {"verifyChecksum": true}}} }'
NGINX Ingress Controller appends .sha256 to the bundle URL automatically.
verifyChecksumis only supported for HTTPS sources. NGINX Instance Manager and NGINX One Console sources use native integrity checks.
Security log profile bundles can also be fetched from a remote source using apLogBundleSource in securityLogs[]:
kubectl apply -f - <<EOF
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: waf-policy
spec:
waf:
enable: true
apBundleSource:
type: HTTPS
url: "https://bundles.example.com/waf/my-policy.tgz"
enablePolling: true
pollInterval: "5m"
securityLogs:
- enable: true
apLogBundleSource:
type: HTTPS
url: "https://bundles.example.com/waf/my-log-profile.tgz"
enablePolling: true
pollInterval: "5m"
logDest: "syslog:server=syslog-svc.default:514"
EOFVerify log events are arriving at your syslog destination:
kubectl exec -it <SYSLOG_POD> -- cat /var/log/messagesWhen a bundle cannot be fetched on the first attempt:
- A Warning event is emitted on the Policy resource.
- The Policy status is updated with the error details.
- Any VirtualServer referencing the Policy returns HTTP 500 until the bundle arrives.
Check the events for details:
kubectl describe policy waf-policyUpdate the Policy with a corrected URL, credentials, or policyName. NGINX Ingress Controller detects the change and retries immediately. Once the bundle is fetched, WAF protection becomes active and the VirtualServer stops returning 500.
If polling is enabled and a poll cycle fails after a previous successful fetch, the existing bundle remains active. WAF protection continues without interruption. A Warning event is emitted, and NGINX Ingress Controller retries on the next poll cycle.