nginx fails to start with filter expression parse error after CI templating

Problem – Nginx Fails to Start with “filter expression parse error” After CI Templating

During the CI/CD pipeline the Nginx configuration is generated from a Jinja‑like template that is populated with environment‑specific variables. After the deployment step the nginx service aborts with a filter expression parse error, preventing the web tier from coming online.

Typical symptom in the CI job log:


$ docker run --rm -v /etc/nginx/conf.d:/etc/nginx/conf.d nginx:1.25 nginx -t
nginx: [emerg] 1#0: filter expression parse error: "if ($request_uri ~* ^/api/v1/(.*)$) {"
nginx: configuration file /etc/nginx/nginx.conf test failed

The same configuration works when edited manually, but the automated rendering step consistently produces the failing line.

Root Cause – Templating Engine Emits Unescaped Dollar Signs

The CI pipeline uses an AI‑assisted templating stage that substitutes variables with ${VAR} syntax. Nginx’s own configuration language also uses the $ prefix for built‑in variables (e.g., $request_uri, $host). The templating engine treats those as placeholders and attempts to replace them, but because the corresponding CI variables are undefined, it removes the leading $ or inserts an empty string. The resulting line is syntactically invalid:


# Rendered by CI (incorrect)
if (request_uri ~* ^/api/v1/(.*)$) {
    proxy_pass http://backend;
}

When the configuration is parsed, Nginx cannot find a variable named request_uri, leading to the “filter expression parse error”. The underlying issue is the lack of proper escaping for Nginx’s native variables during template rendering.

Debug – Investigation Steps

  1. Collect the generated configuration from the build artifact.
  2. Compare with a known‑good manual version.
  3. Run Nginx syntax check locally.

Example commands used in the pipeline:


# Show the rendered file
cat /tmp/nginx/conf.d/app.conf | nl -ba | sed -n '1,30p'

# Validate syntax
nginx -t -c /tmp/nginx/nginx.conf

Sample output showing the malformed line:


nginx: [emerg] 1#0: filter expression parse error: "if (request_uri ~* ^/api/v1/(.*)$) {"

To isolate the templating problem, a minimal template was rendered with only the problematic if block. The result confirmed that the $ character was stripped.

Solution – Properly Escape Nginx Variables in CI Templates

The fix consists of two parts:

1. Update the templating syntax to use a literal $$ escape sequence for Nginx variables.

Most templating engines (e.g., envsubst, gomplate, helm) treat $$ as an escaped dollar sign.

Before (broken template snippet):


server {
    listen 80;
    location / {
        if ($request_uri ~* ^/api/v1/(.*)$) {
            proxy_pass http://${BACKEND_HOST};
        }
    }
}

After (corrected template snippet):


server {
    listen 80;
    location / {
        if ($$request_uri ~* ^/api/v1/(.*)$) {
            proxy_pass http://${BACKEND_HOST};
        }
    }
}

During rendering, $$request_uri becomes $request_uri in the final file, while ${BACKEND_HOST} is still substituted with the CI variable.

2. Add a validation step in the pipeline to catch missing escapes before deployment.

Insert a nginx -t check after templating and before the Docker image is built:


# Render template to a temporary location
render-template.sh template.conf.j2 /tmp/rendered.conf

# Validate Nginx syntax
nginx -t -c /tmp/nginx.conf -g "daemon off;" || exit 1

If the syntax test fails, the CI job aborts, preventing a broken image from reaching production.

Verify – Confirming the Fix

After applying the escape changes, re‑run the CI job and inspect the generated file:


cat /tmp/nginx/conf.d/app.conf | grep -A2 "if"
if ($request_uri ~* ^/api/v1/(.*)$) {
    proxy_pass http://backend-prod;
}

Run the syntax check again:


nginx -t -c /tmp/nginx/nginx.conf
nginx: the configuration file /tmp/nginx/nginx.conf syntax is ok
nginx: configuration file /tmp/nginx/nginx.conf test is successful

Deploy the updated image to a staging environment and perform a functional test (e.g., curl -I http://staging.example.com/api/v1/test) to ensure the request is proxied correctly.

Prevent – Guardrails for Future Templating Errors

  • Template linting: Use a linter (e.g., yamllint for Helm, custom regex checks) that flags any unescaped $ not followed by a known CI variable pattern.
  • CI policy: Enforce a “render‑then‑test” stage in every pipeline that runs nginx -t on the rendered config.
  • Documentation: Clearly annotate templates with comments indicating that Nginx variables must be escaped as $$.
  • Version control: Store the raw template and a rendered example side‑by‑side to make diff reviews easier.
  • Automated regression test: Include an integration test that sends a request through Nginx and validates the response code and headers.

FAQ – Common Follow‑Up Questions

  1. Why does the error only appear after CI templating and not when I edit the file locally?
    Because local edits bypass the templating engine, so the $ characters remain intact. The CI step strips or replaces them, producing invalid syntax.
  2. Can I use a different placeholder syntax to avoid escaping?
    Yes. Some teams switch to double‑curly braces (e.g., {{ BACKEND_HOST }}) for CI variables and reserve the plain $ for Nginx. This requires updating the rendering tool configuration.
  3. What if I need to embed a literal ${ sequence in a response header?
    Escape the dollar sign twice: $$\{. After rendering the first pass, it becomes $\{, which Nginx will treat as a literal string.
  4. Is there a way to make Nginx ignore malformed if blocks instead of failing?
    No. Nginx treats configuration parsing errors as fatal. The only reliable approach is to ensure the configuration is syntactically correct before loading.
  5. How do I debug similar issues when the offending line is deep inside an included file?
    Run nginx -T to dump the full, merged configuration. Search for the problematic pattern, then trace back to the originating template file.

Related Topic Hub: Distributed Systems Troubleshooting Hub