## TL;DR
An unsubstituted template variable silently became the wrong gateway address. Render every config, validate that no placeholders remain, and show a dry-run diff before applying.

## The query
```text
vpn-config agent applied the wrong gateway: template variable not substituted
```

## Use this when
- deployed VPN config points at the wrong gateway
- the config contains a literal placeholder string
- template changes preceded the bad deploy

## Not for
- gateway outages or performance
- client-side VPN failures
- certificate problems on the gateway

## Steps
1. Pull the deployed config and find the unsubstituted variable. Expected output: the raw placeholder is visible in the live config.
2. Add a post-render validation that fails the job if any placeholder pattern remains. Expected output: unrendered templates never deploy.
3. Require a dry-run diff showing old vs new gateway before every apply. Expected output: a human or check sees the change first.
4. Fix the variable source: the missing value, the typo in the variable name, or the wrong environment file. Expected output: the variable resolves correctly.
5. Redeploy and verify the active gateway matches intent. Expected output: the correct gateway is in use.

## Applies to
VPN configuration automation, Jinja or similar templating, Palo Alto, Cisco, Fortinet gateway configs.

## Variant phrasings
### Variable substituted but with the wrong environment value
Namespace variables per environment and validate the environment selection.

### Partial substitution across a config set
Render and validate the whole set atomically, not file by file.

## Why it happens
Templating fails open: a missing variable becomes an empty string or a literal, and the deploy proceeds. Validation after rendering is the only reliable catch.

## Edge cases
- Test templates with intentionally missing variables in staging.
- Version the rendered configs so a bad deploy can be diffed and rolled back.
- Alert on gateway changes; they should be rare and deliberate.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_IeOIpqHx-Veo6nEhF4jEhQ
