Subchapter 94.7
references/monitoring-and-troubleshooting-tunnels.mdMarkdown20 KBView on GitHub
Domain expertise for detecting and diagnosing AWS Site-to-Site VPN tunnel failures with Amazon
CloudWatch. Covers the TunnelState metric and how it reads differently for static versus BGP
connections, building alarms that notify an Amazon Simple Notification Service (SNS) topic, enabling
and reading Site-to-Site VPN logs to find the cause, why the data metrics are not a liveness signal,
and the CloudWatch cost consideration. This reference covers tunnel state and VPN logs; AWS CloudTrail
captures the API-level events such as connection creation and modification.
Does not cover building the connection or its high availability (other references). Assumes a connection exists (the creating-a-site-to-site-vpn-connection reference).
Execute commands using the AWS MCP server when connected (sandboxed execution, audit logging,
observability). Fall back to the AWS CLI otherwise. Pass --region {region} matching the connection.
To detect and diagnose tunnel failures, set the right alarm against the connection’s routing type, wire it to an SNS topic, and enable VPN logs to find the cause when a tunnel drops. See the Procedure section below.
The procedure covers:
TunnelState threshold is correctThe TunnelState metric does not read the same way for both routing types. For static VPNs, 0 is
DOWN and 1 is UP. For BGP VPNs, 1 means ESTABLISHED and values between 0 and 1 mean at least one
tunnel is not up. An alarm threshold set without knowing this misfires or stays silent during a real
outage.
Constraints:
A working alarm needs the right metric, statistic, threshold, and an SNS topic wired together, and the statistic depends on the alarm scope: alarming when any tunnel drops versus only when all tunnels are down versus watching one specific tunnel.
Constraints:
Minimum for any-tunnel-down, Maximum for all-tunnels-down, or the TunnelIpAddress dimension for a single tunnelWhen a tunnel is down the metric says it is down but not why, and customers spend time guessing at IKE, dead peer detection, or BGP causes. Site-to-Site VPN can publish two separate, independently enabled log types to CloudWatch Logs to help with troubleshooting (the same two names the AWS docs use — see AWS Site-to-Site VPN logs (opens in a new tab)):
LogEnabled / LogGroupArn /
LogOutputFormat fields.BGPStatus (session state
transitions such as OpenConfirm→Established, prefix-limit warnings and violations, hold-timer
expiry, and Cease notifications) and RouteStatus (routes ADVERTISED / UPDATED / WITHDRAWN, with
a details field when a route is denied). They apply only to dynamic (BGP) connections and are
meaningless on a static connection. Controlled by the separate BgpLogEnabled / BgpLogGroupArn
/ BgpLogOutputFormat fields.Both are toggled through modify-vpn-tunnel-options, but they are distinct switches: enabling
tunnel activity logging does NOT enable BGP logging. A dynamic connection whose tunnel stays up at
the IKE layer but drops its BGP session (prefix-limit hit, hold-timer expiry, route withdrawn) shows
nothing useful in the activity log — only the BGP log names that cause. This is exactly the trap of
enabling “the VPN logs” and quietly getting activity logs alone.
Constraints:
modify-vpn-tunnel-options, rather than defaulting to activity logs aloneThe TunnelDataIn and TunnelDataOut metrics can report traffic even when a tunnel is down,
because of periodic status checks and background BGP and ARP requests. Customers who watch data
volume as a health signal conclude the tunnel is fine when it is not.
Constraints:
TunnelState for health and treat the data metrics as usage, not livenessSite-to-Site VPN does not charge for metrics or logs, but the CloudWatch metrics, alarms, and logs themselves bill at standard CloudWatch rates. Turning on detailed monitoring and log delivery across every connection adds up.
Constraints:
The threshold does not match the routing type. Set it against static (0/1) or BGP (1 = established) (TunnelState reads differently by routing type).
The SNS topic has no confirmed subscription. Confirm the subscription (Wiring a working alarm).
VPN logs are not enabled, or only one of the two types is. Enable the right type(s) — tunnel activity logs for IKE/IPsec detail, BGP logs for BGP session and route detail on dynamic connections — and read them (Publish tunnel activity and BGP logs to CloudWatch).
The IKE tunnel is up but the BGP session or route exchange failed, and only activity logging was enabled so the cause is invisible. Enable BGP logs and read the BGP status and route-status fields (Publish tunnel activity and BGP logs to CloudWatch).
Data metrics report background traffic. Use TunnelState for health (Data metrics are not liveness).
This procedure determines the routing type, creates a correctly-thresholded alarm wired to an SNS topic, and enables VPN logs, then surfaces the connection console link to verify tunnel status.
static or dynamic (BGP), to set the threshold correctly.any-tunnel-down, all-tunnels-down, or single-tunnel, to pick
the statistic and dimension (see Step 2). any-tunnel-down pages when either tunnel of the
connection drops (redundancy health); all-tunnels-down pages only when the whole connection is
lost; single-tunnel watches one specific tunnel by its outside IP.describe-vpn-connections).activity, bgp, or both.
Ask the customer explicitly. activity (tunnel activity log: IKE/IPsec/DPD/NAT-T) applies to any
connection; bgp (tunnel BGP log: session and route events) applies only to dynamic connections.
For a dynamic connection, recommend both; for a static connection this is forced to activity.staging or test, to weigh the CloudWatch cost.Constraints for parameter acquisition:
activity, bgp, or both) before
modifying any tunnel; for a dynamic connection recommend both, and for a static connection
enable only activity and do not offer bgpConstraints:
aws sts get-caller-identityTunnelState threshold against the routing type: static is 0 DOWN / 1 UP; BGP is 1 ESTABLISHED, 0 to 1 means a tunnel is not upConstraints:
You MUST create the alarm with the right metric, statistic for the scope, threshold, and SNS topic:
aws cloudwatch put-metric-alarm --alarm-name s2s-vpn-tunnel-down-{vpn_connection_id}-{scope} \
--namespace AWS/VPN --metric-name TunnelState --dimensions Name=VpnId,Value={vpn_connection_id} \
--statistic Maximum --threshold 1 --comparison-operator LessThanThreshold \
--period 300 --evaluation-periods 1 --treat-missing-data breaching \
--alarm-actions {sns_topic_arn} --region {region}You MUST verify that all SNS topic subscribers are authorized operations personnel before wiring the topic to the alarm
You MUST verify the SNS topic has server-side encryption enabled before wiring it to the alarm, checking that KmsMasterKeyId is set on the topic:
aws sns get-topic-attributes --topic-arn {sns_topic_arn} --region {region}You MUST set --treat-missing-data breaching so the alarm fires rather than going to INSUFFICIENT_DATA when a down tunnel stops reporting metrics
You MUST include both {vpn_connection_id} and {scope} in the alarm name so each
connection-and-scope alarm is unique; put-metric-alarm is an upsert, so a name without the scope
would let a second alarm on the same connection (for example an any-tunnel-down alarm added
alongside an all-tunnels-down alarm) silently overwrite the first, and a name without the
connection id would collide across connections (for example the second leg of an HA pair)
You MUST pick the statistic and dimension that match the requested scope. With the Name=VpnId
dimension, TunnelState aggregates across both tunnels of the connection, so the statistic
selects which aggregate you alarm on:
scope | Intent | Statistic | Dimension |
|---|---|---|---|
any-tunnel-down | Page when either tunnel drops (redundancy health) | Minimum (falls below 1 as soon as one tunnel is down) | Name=VpnId |
all-tunnels-down | Page only when the whole connection is lost | Maximum (falls below 1 only when no tunnel is up) | Name=VpnId |
single-tunnel | Watch one specific tunnel | statistic does not matter (already one tunnel) | Name=VpnId plus Name=TunnelIpAddress,Value={tunnel_outside_ip} |
The example below uses Maximum (the all-tunnels-down scope), which alarms only on full
connection loss; switch to Minimum for any-tunnel-down to be paged the moment either tunnel
drops, or add the TunnelIpAddress dimension for single-tunnel
There are two independent log types (see “Publish tunnel activity and BGP logs to CloudWatch”):
tunnel activity logs (LogEnabled, IKE/IPsec/DPD, any connection) and tunnel BGP logs
(BgpLogEnabled, BGP session and route events, dynamic connections only). They are separate
switches on the same modify-vpn-tunnel-options call, and enabling one does not enable the other.
Constraints:
You MUST confirm with the customer which log type(s) to enable — activity, bgp, or both —
before running modify-vpn-tunnel-options. Do NOT silently enable activity logs alone. For a
dynamic (BGP) connection, recommend both because BGP-layer failures (prefix-limit hits,
hold-timer expiry, withdrawn routes) do not appear in the activity log. For a static
connection, enable only activity and do not offer bgp.
You MUST create the CloudWatch Logs group with AWS KMS encryption, then enable log delivery to it on each tunnel so a future down tunnel can be diagnosed. create-log-group does not return an ARN, so construct the log group ARN as arn:aws:logs:{region}:{account_id}:log-group:/aws/vpn/{vpn_connection_id}:* (or retrieve it with describe-log-groups), and run modify-vpn-tunnel-options once per tunnel outside IP, since each connection has two tunnels:
aws logs create-log-group --log-group-name /aws/vpn/{vpn_connection_id} \
--kms-key-id {kms_key_arn} --region {region}You MUST build the LogOptions string from the customer’s log_types choice, setting only the
fields for the log types they chose. Run once per address in {tunnel_outside_ips}:
activity only:
--tunnel-options "LogOptions={CloudWatchLogOptions={LogEnabled=true,LogGroupArn={log_group_arn},LogOutputFormat=json}}"bgp only (dynamic connections only):
--tunnel-options "LogOptions={CloudWatchLogOptions={BgpLogEnabled=true,BgpLogGroupArn={log_group_arn},BgpLogOutputFormat=json}}"both (recommended for dynamic connections):
--tunnel-options "LogOptions={CloudWatchLogOptions={LogEnabled=true,LogGroupArn={log_group_arn},LogOutputFormat=json,BgpLogEnabled=true,BgpLogGroupArn={log_group_arn},BgpLogOutputFormat=json}}"Full call, repeated per tunnel outside IP:
aws ec2 modify-vpn-tunnel-options --vpn-connection-id {vpn_connection_id} \
--vpn-tunnel-outside-ip-address {tunnel_outside_ip} \
--tunnel-options "{log_options_for_chosen_types}" \
--region {region}You MUST warn the customer that modify-vpn-tunnel-options briefly renegotiates and interrupts the affected tunnel while the change is applied; on a production connection, enable logs one tunnel at a time and confirm the first tunnel is back up before modifying the second, so the connection is never fully down
You SHOULD point the customer at the IKE fields (activity log) and the BGP status / route fields (BGP log) that name the cause; see AWS Site-to-Site VPN logs (opens in a new tab) for the field reference and Enable Site-to-Site VPN logs (opens in a new tab) for the console flow
Constraints:
You MUST present the VPN connection console link, filling {region} and {vpnConnectionId} from the request, and tell the customer to open it and confirm tunnel status:
https://console.aws.amazon.com/vpc/home?region={region}#VpnConnectionDetails:VpnConnectionId={vpnConnectionId}{
"region": "us-east-1",
"vpn_connection_id": "vpn-0abc1234def567890",
"routing_type": "dynamic",
"scope": "all-tunnels-down",
"sns_topic_arn": "arn:aws:sns:us-east-1:111122223333:vpn-alerts",
"kms_key_arn": "arn:aws:kms:us-east-1:111122223333:key/abcd1234-ef56-7890-abcd-1234567890ab",
"tunnel_outside_ips": ["198.51.100.10", "198.51.100.11"],
"log_types": "both",
"environment": "staging"
}BGP connection: TunnelState threshold set to alarm below 1 (established). all-tunnels-down alarm
(Maximum statistic, VpnId dimension) wired to the vpn-alerts SNS topic. Confirmed the connection is
dynamic and asked which logs to enable; enabled BOTH tunnel activity logs (IKE/IPsec) and BGP logs
to a KMS-encrypted CloudWatch Logs group on both tunnels (198.51.100.10, 198.51.100.11), so a
BGP-layer failure is not left invisible. Noted CloudWatch cost is justified for
the staging workload. Open the connection to confirm tunnel status:
https://console.aws.amazon.com/vpc/home?region=us-east-1#VpnConnectionDetails:VpnConnectionId=vpn-0abc1234def567890Threshold does not match routing type. Set it against static or BGP (Step 1).
SNS topic has no confirmed subscription. Confirm it (Step 2).
Enable VPN logs and read the IKE and BGP fields (Step 3).
Use TunnelState, not data metrics, for health (Data metrics are not liveness).
VPN logs and tunnel state changes expose infrastructure detail (peer IPs, BGP and IKE events), so the monitoring path is itself sensitive and must be access-controlled.
Constraints: