# VPN Connection Idle -- No Tunnels Up

> AWS Site-to-Site VPN bills a fixed connection-hour rate from the moment a connection exists, tunnels up or not. ZopNight requires 3 fully-covered CloudWatch axes over 30 days (TunnelState peaking at zero plus combined tunnel bytes under 1 MiB) before calling a connection idle and claiming its full measured cost.

Source: https://zop.dev/integrations/aws/recommendations/vpn-connection-idle-no-tunnels-up
Updated: 2026-08-19

---

## Dead on both ends, billed in the middle

A Site-to-Site VPN whose customer gateway was decommissioned (the office closed, the
appliance replaced, the migration finished) keeps billing its [connection-hour fee](https://aws.amazon.com/vpn/pricing/)
indefinitely. Nothing on the AWS side notices that the far end no longer exists. These
connections are common leftovers of network migrations, and the fee runs until someone
deletes the connection object itself.

## Three axes, all mandatory

Idleness here is corroborated across [the AWS/VPN metrics a connection publishes](https://docs.aws.amazon.com/vpn/latest/s2svpn/monitoring-cloudwatch-vpn.html), and any one of them can veto.
TunnelState is the health gauge: the rule requires its window *peak* to be zero, meaning no
tunnel was up at any moment in 30 days. One minute of connectivity anywhere in the window
disqualifies. TunnelDataIn and TunnelDataOut are the traffic axes: their combined peak must
sit at or below a 1 MiB floor, tolerating the keep-alive and dead-peer-detection chatter an
up-but-unused tunnel emits while any real transfer disqualifies. Each axis must be present
with a full 30 days of coverage; a missing or thin series abstains, because a connection you
failed to measure is not a connection proven dead.

## The cost side has the same discipline

The saving is the connection's measured monthly fixed fee from the pricing pipeline, never
a published-rate guess, and an unpriced connection abstains. Only connections in the
available state are considered at all; anything mid-provisioning or deleting is in a
different lifecycle and out of scope.

## Reproduce the tunnel check

```bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/VPN --metric-name TunnelState \
  --dimensions Name=VpnId,Value=vpn-0123456789abcdef0 \
  --start-time "$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 3600 --statistics Maximum
```

Every datapoint at 0 is the no-tunnel-ever-up condition; repeat with TunnelDataIn and
TunnelDataOut for the traffic axes.

## Deleting the whole chain

The connection is only part of the bill of materials. After deleting it, check whether the
virtual private gateway it attached to serves any other connection (an orphaned VGW is the
next cleanup), and remove static routes referencing the VPN from your route tables. If the
link is meant to come back someday, the honest comparison is redeploying the connection when
needed versus paying the hourly fee to keep a dead one warm.
