Debugging and Troubleshooting
Common Failure Modes
NGINX Reload Failure
Symptom: Controller logs show "reload failed" or NGINX returns error status.
Diagnosis:
- Check controller logs for the generated config that failed
- Look for
nginx -t output in logs — shows exact syntax error and line number
- Common causes:
- Unsanitized user string injected into config (missing
containsDangerousChars() check)
- Template guard missing (
{{- if }} / {{- with }}) for optional field
- Duplicate directive from conflicting policies
- Invalid upstream when no endpoints available
Fix pattern:
- Find the template or config generation code that produced the bad directive
- Add validation to reject the input earlier, OR fix the template guard
- Verify with
make test — snapshot tests catch most template output issues
CRD Not Taking Effect
Symptom: User applies VirtualServer/Policy but NGINX config doesn't change.
Diagnosis:
- Check CRD status:
kubectl get vs <name> -o yaml — look at .status.message
- Check controller logs for sync errors on that resource
- Common causes:
- Validation rejecting the resource (check
status.state: Invalid)
- Missing secret reference (TLS, JWT, OIDC secrets)
- Policy referenced but not found in namespace
- Resource conflicts (duplicate host/path)
Controller Crash / Panic
Symptom: Pod restarts, panic in logs.
Diagnosis:
- Check logs for the panic stack trace
- Common causes:
- Nil pointer on optional CRD field (forgot
*bool/*int check)
- Map access without nil check on
.Spec.X field
- Race condition in concurrent secret/config access
- Look for the file:line in the stack trace → usually in
internal/configs/ or internal/k8s/
Snapshot Test Failure
Symptom: make test fails with snapshot mismatch.
Diagnosis:
- This means template output changed — could be intentional or regression
- Review the diff shown in test output
- If change is intentional:
make test-update-snaps, then re-read git diff -- '**/__snapshots__/**' to confirm only the expected directives moved
- If change is unintentional: your template edit had side effects — fix the template
The inverse failure is more dangerous: you edited a .tmpl and make test-update-snaps produced no diff. That is not a pass — it means no fixture sets the field your new branch depends on. Add the fixture in template_test.go / templates_test.go and regenerate.
Log Locations
| Context |
Location |
What to look for |
| Controller logs |
Pod stdout/stderr |
Sync errors, reload status, validation failures |
| NGINX error log |
/var/log/nginx/error.log in container |
Config syntax errors, upstream failures |
| NGINX access log |
/var/log/nginx/access.log in container |
Request routing verification |
Validation and Diagnostic Tools
| Tool |
Command |
Purpose |
| Config test |
nginx -t (inside container) |
Validate NGINX config syntax |
| CRD status |
kubectl get vs,vsr,ts,pol -A |
Check resource state |
| Controller logs |
kubectl logs <pod> -n nginx-ingress |
Runtime errors |
| Describe events |
kubectl describe vs <name> |
Kubernetes events for the resource |
| Generated config |
kubectl exec <pod> -- cat /etc/nginx/conf.d/<file> |
Inspect actual generated NGINX config |
Debugging Workflow
- Reproduce — Get the exact error. Is it a reload failure? Wrong routing? Crash?
- Assess security impact — Before diving into the fix, ask:
- Is this bug exploitable? (Can external input trigger it?)
- Does the failure expose sensitive data in logs or error messages?
- Could an attacker craft input to reach this code path?
- If exploitable: flag for security review BEFORE fixing
- Locate the layer — Use logs and status to determine:
- Validation layer? →
status.state: Invalid with reason
- Config generation? → Generated config has wrong directives
- Template? → Snapshot test shows the issue
- Controller? → Sync error in logs, resource not processed
- Isolate — Find minimum CRD/annotation that triggers the issue
- Fix — Make the change in the correct layer (don't fix templates for validation bugs)
- Verify —
make test passes, snapshot output is correct
- Prevent — Add a test case that would catch this regression (include negative/malicious input tests if the bug was in a validation path)
Config Generation Debugging
When the generated NGINX config is wrong:
- Find the template struct — Which struct feeds the template? Check
internal/configs/version2/http.go (VS) or internal/configs/version1/config.go (Ingress)
- Find the config generator — Where is the struct populated? Check
internal/configs/virtualserver.go or internal/configs/ingress.go
- Find the template — Which
.tmpl file renders it? Check internal/configs/version2/nginx-plus.virtualserver.tmpl or the OSS variant
- Add a snapshot test — Create a test case in the appropriate
_test.go file with the input that triggers the bug, run make test-update-snaps to capture current (wrong) output, then fix and regenerate
Common Gotchas When Debugging
- NGINX config errors show line numbers in the GENERATED file, not your template — map back manually
- Secret-related failures often show as "file not found" in NGINX logs (secret not written to filesystem yet)
- Policy ordering matters — first matching policy wins, check
generatePolicies() logic
- Plus-only features will work in Plus template but silently produce invalid config in OSS template
containsDangerousChars() failures are validation errors and typically result in status.state: Invalid — check the CRD status message and controller logs
Security-Sensitive Debugging
When debugging an issue that involves user-provided input reaching NGINX config:
- Trace the input path — From CRD field / annotation → validation → config struct → template → NGINX config file. Identify every point where sanitization SHOULD happen.
- Check for injection — Can crafted input inject NGINX directives? Look for
;, {, }, $, newlines, backticks in the user-controlled value.
- Verify the guard — Does
containsDangerousChars() or ValidateEscapedString() cover this path? If not, the bug is a security vulnerability.
- Never log secrets — When debugging TLS/JWT/OIDC issues, mask credential values. Log key names and paths, not contents.
- Check RBAC — If the issue involves unauthorized access, verify ServiceAccount permissions and RBAC role bindings before looking at code.
1---2name: nic-debugging3description: Debugging and troubleshooting patterns for NIC. Use when diagnosing failures, tracing issues, investigating NGINX reload errors, config generation bugs, or controller sync problems.4---56# Debugging and Troubleshooting78## Common Failure Modes910### NGINX Reload Failure1112**Symptom:** Controller logs show "reload failed" or NGINX returns error status.1314**Diagnosis:**15161. Check controller logs for the generated config that failed172. Look for `nginx -t` output in logs — shows exact syntax error and line number183. Common causes:19 - Unsanitized user string injected into config (missing `containsDangerousChars()` check)20 - Template guard missing (`{{- if }}` / `{{- with }}`) for optional field21 - Duplicate directive from conflicting policies22 - Invalid upstream when no endpoints available2324**Fix pattern:**2526- Find the template or config generation code that produced the bad directive27- Add validation to reject the input earlier, OR fix the template guard28- Verify with `make test` — snapshot tests catch most template output issues2930### CRD Not Taking Effect3132**Symptom:** User applies VirtualServer/Policy but NGINX config doesn't change.3334**Diagnosis:**35361. Check CRD status: `kubectl get vs <name> -o yaml` — look at `.status.message`372. Check controller logs for sync errors on that resource383. Common causes:39 - Validation rejecting the resource (check `status.state: Invalid`)40 - Missing secret reference (TLS, JWT, OIDC secrets)41 - Policy referenced but not found in namespace42 - Resource conflicts (duplicate host/path)4344### Controller Crash / Panic4546**Symptom:** Pod restarts, panic in logs.4748**Diagnosis:**49501. Check logs for the panic stack trace512. Common causes:52 - Nil pointer on optional CRD field (forgot `*bool`/`*int` check)53 - Map access without nil check on `.Spec.X` field54 - Race condition in concurrent secret/config access553. Look for the file:line in the stack trace → usually in `internal/configs/` or `internal/k8s/`5657### Snapshot Test Failure5859**Symptom:** `make test` fails with snapshot mismatch.6061**Diagnosis:**62631. This means template output changed — could be intentional or regression642. Review the diff shown in test output653. If change is intentional: `make test-update-snaps`, then re-read `git diff -- '**/__snapshots__/**'` to confirm only the expected directives moved664. If change is unintentional: your template edit had side effects — fix the template6768**The inverse failure is more dangerous:** you edited a `.tmpl` and `make test-update-snaps` produced **no** diff. That is not a pass — it means no fixture sets the field your new branch depends on. Add the fixture in `template_test.go` / `templates_test.go` and regenerate.6970## Log Locations7172| Context | Location | What to look for |73| --- | --- | --- |74| Controller logs | Pod stdout/stderr | Sync errors, reload status, validation failures |75| NGINX error log | `/var/log/nginx/error.log` in container | Config syntax errors, upstream failures |76| NGINX access log | `/var/log/nginx/access.log` in container | Request routing verification |7778## Validation and Diagnostic Tools7980| Tool | Command | Purpose |81| --- | --- | --- |82| Config test | `nginx -t` (inside container) | Validate NGINX config syntax |83| CRD status | `kubectl get vs,vsr,ts,pol -A` | Check resource state |84| Controller logs | `kubectl logs <pod> -n nginx-ingress` | Runtime errors |85| Describe events | `kubectl describe vs <name>` | Kubernetes events for the resource |86| Generated config | `kubectl exec <pod> -- cat /etc/nginx/conf.d/<file>` | Inspect actual generated NGINX config |8788## Debugging Workflow89901. **Reproduce** — Get the exact error. Is it a reload failure? Wrong routing? Crash?912. **Assess security impact** — Before diving into the fix, ask:92 - Is this bug exploitable? (Can external input trigger it?)93 - Does the failure expose sensitive data in logs or error messages?94 - Could an attacker craft input to reach this code path?95 - If exploitable: flag for security review BEFORE fixing963. **Locate the layer** — Use logs and status to determine:97 - Validation layer? → `status.state: Invalid` with reason98 - Config generation? → Generated config has wrong directives99 - Template? → Snapshot test shows the issue100 - Controller? → Sync error in logs, resource not processed1014. **Isolate** — Find minimum CRD/annotation that triggers the issue1025. **Fix** — Make the change in the correct layer (don't fix templates for validation bugs)1036. **Verify** — `make test` passes, snapshot output is correct1047. **Prevent** — Add a test case that would catch this regression (include negative/malicious input tests if the bug was in a validation path)105106## Config Generation Debugging107108When the generated NGINX config is wrong:1091101. **Find the template struct** — Which struct feeds the template? Check `internal/configs/version2/http.go` (VS) or `internal/configs/version1/config.go` (Ingress)1112. **Find the config generator** — Where is the struct populated? Check `internal/configs/virtualserver.go` or `internal/configs/ingress.go`1123. **Find the template** — Which `.tmpl` file renders it? Check `internal/configs/version2/nginx-plus.virtualserver.tmpl` or the OSS variant1134. **Add a snapshot test** — Create a test case in the appropriate `_test.go` file with the input that triggers the bug, run `make test-update-snaps` to capture current (wrong) output, then fix and regenerate114115## Common Gotchas When Debugging116117- NGINX config errors show line numbers in the GENERATED file, not your template — map back manually118- Secret-related failures often show as "file not found" in NGINX logs (secret not written to filesystem yet)119- Policy ordering matters — first matching policy wins, check `generatePolicies()` logic120- Plus-only features will work in Plus template but silently produce invalid config in OSS template121- `containsDangerousChars()` failures are validation errors and typically result in `status.state: Invalid` — check the CRD status message and controller logs122123## Security-Sensitive Debugging124125When debugging an issue that involves user-provided input reaching NGINX config:1261271. **Trace the input path** — From CRD field / annotation → validation → config struct → template → NGINX config file. Identify every point where sanitization SHOULD happen.1282. **Check for injection** — Can crafted input inject NGINX directives? Look for `;`, `{`, `}`, `$`, newlines, backticks in the user-controlled value.1293. **Verify the guard** — Does `containsDangerousChars()` or `ValidateEscapedString()` cover this path? If not, the bug is a security vulnerability.1304. **Never log secrets** — When debugging TLS/JWT/OIDC issues, mask credential values. Log key names and paths, not contents.1315. **Check RBAC** — If the issue involves unauthorized access, verify ServiceAccount permissions and RBAC role bindings before looking at code.