Google IAP tunnel troubleshooting
Most IAP tunnel failures come from one of four things: the firewall rule, the IAM role, the VM's state, or the operating-system login that happens after the tunnel. This page maps the error you see to the cause, in the order to check them.
1. Read the error code first
gcloud reports IAP tunnel failures as Error while connecting [CODE]. Google's FAQ
documents these:
| Code | Google's description | Cause → fix |
|---|---|---|
| 4003 | Connection or firewall issue | Nothing is listening on the port, or the firewall blocks IAP. Google: "Check that your VM process is running and listening on the expected port. Also verify that your firewall rules allow connections on that port." → confirm sshd/RDP is running; ensure an ingress rule allows 35.235.240.0/20 to that port. |
| 4010 | Connection closed by destination | The VM ended the connection. Google: "Reset the VM. If problems persist, examine auth.log (usually in /var/log/) or use the serial console." → look for sshd rejecting the connection (MaxStartups, AllowUsers, host key issues). |
| 4033 | Permission, existence, or VM state issue | Google: "Confirm you have the Tunnel User role assigned for the resource through the IAP page, and verify the VM exists and is running." → the role must be on this instance (or the project); check the zone and instance name too. |
| 4047 | Instance doesn't exist or is stopped | Google: "Make sure your VM is powered on and has fully completed its startup sequence." |
2. Firewall-rule mistakes
The rule must be ingress, allow, for the exact port, with source range
35.235.240.0/20 (IPv6 VMs: 2600:2d00:1:7::/64), on the network the VM is in:
gcloud compute firewall-rules list --filter="sourceRanges:35.235.240.0/20" \
--format="table(name,network,sourceRanges.list(),allowed[].map().firewall_rule().list(),targetTags.list())"
Common slips: rule created on default while the VM is on another VPC; a --target-tags
value the VM doesn't carry; a higher-priority deny rule; port 22 allowed but you're
tunnelling 3389; the VM's OS firewall (Windows Defender Firewall, ufw) blocking the port
even though the VPC rule is right.
3. Role and scope mistakes
- The user lacks
roles/iap.tunnelResourceAccessor, or has it on a different instance. Per-instance grants are visible on Security → Identity-Aware Proxy → SSH and TCP Resources. - An IAM condition excludes this attempt — e.g.
destination.port == 22while you're opening 3389, or an access level (Context-Aware Access) that your device or network doesn't satisfy. The audit log entry will showauthorizationInfo.granted: false. gcloud compute sshadditionally needscompute.instances.getandcompute.instances.list(part ofroles/compute.instanceAdmin.v1); without them it fails before the tunnel even starts.- Tags don't work for IAP tunnel permissions — if someone "granted by tag", nothing was granted.
Check what IAP decided:
protoPayload.serviceName="iap.googleapis.com"
in Logs Explorer, then inspect authorizationInfo.granted and principalEmail.
4. "Permission denied" after the tunnel opens
If the tunnel connects but SSH says Permission denied (publickey) or RDP rejects the
password, IAP is working — the OS refused you.
- OS Login enabled: you need
roles/compute.osLogin(orosAdminLogin); metadata keys are ignored. - OS Login disabled: your public key must be in instance/project metadata;
gcloud compute sshadds it if you holdcompute.instanceAdmin.v1. - Windows: wrong local account or password →
gcloud compute reset-windows-password(see RDP via IAP).
5. It connects, then drops or crawls
- Drops after ~an hour of quiet: IAP closes idle sessions after one hour of inactivity.
Use
ServerAliveIntervalfor SSH, or reconnect. - Slow uploads: install NumPy for gcloud's Python (
$(gcloud info --format="value(basic.python_location)") -m pip install numpy, thenexport CLOUDSDK_PYTHON_SITEPACKAGES=1). - Large transfers throttled: by design — IAP TCP forwarding isn't for bulk data and may be rate-limited.
6. Can't even reach IAP
Corporate proxies or DNS filters sometimes block the tunnel endpoints. Google lists the
domains IAP uses as tunnel.cloudproxy.app and mtls.tunnel.cloudproxy.app (the latter
when certificate-based access is enabled). Allow them outbound on 443.
7. Checklist
- Is the VM running? (
4047) - Is there an ingress allow rule for
35.235.240.0/20on this port, on this network? (4003) - Is the service listening on that port, and does the OS firewall allow it?
(
4003) - Does the user hold
roles/iap.tunnelResourceAccessoron this instance or the project, with no excluding condition? (4033) - Can the client reach
tunnel.cloudproxy.app:443? - Tunnel up but login refused → OS Login roles / keys / Windows password.
Common questions
What does "failed to connect to backend" mean?
It's the 4003 family: IAP reached the VM's network but nothing accepted the connection — service not listening, or a firewall (VPC or in-guest) blocking the port.
Why does it work for me but not a colleague?
Almost always IAM: they lack the Tunnel User role on that instance, or a condition (port, access level) excludes them. Compare the two audit-log entries.
I have the role on the project — why 4033?
Confirm the VM exists in the zone you passed and is running; 4033 also covers "VM doesn't exist / not running". Then check for a deny policy or an access-level condition.
Can I test without my app?
Yes: gcloud compute start-iap-tunnel VM 22 --local-host-port=localhost:2222 --zone=ZONE
then nc -vz localhost 2222. If that succeeds, the tunnel is fine and the problem is
higher up.