If your GCP VMs don't have external IPs (or you use IAP for security), gcloud compute ssh --tunnel-through-iap works — but it's verbose. You can't use it with scp, rsync, Ansible, or VSCode Remote-SSH easily.
This guide shows how to set up ~/.ssh/config so you can just run:
ssh my-vmand it tunnels through IAP automatically.
Why not
gcloud compute config-ssh? It doesn't support IAP. It generates config with external IPs, which won't work for VMs behind IAP with no public IP.
Add this to ~/.ssh/config:
Host my-vm
ProxyCommand gcloud compute start-iap-tunnel VM_NAME %p --listen-on-stdin --zone=ZONE --project=PROJECT_ID
IdentityFile ~/.ssh/google_compute_engine
User USERNAME
UserKnownHostsFile ~/.ssh/google_compute_known_hosts
IdentitiesOnly yes
CheckHostIP noThe key line is ProxyCommand — it uses gcloud compute start-iap-tunnel with --listen-on-stdin to pipe the SSH connection through IAP instead of connecting to an external IP.
Replace:
my-vm— your alias (whatever you want afterssh)VM_NAME— GCP instance nameZONE— e.g.,us-east1-bPROJECT_ID— e.g.,my-project-prodUSERNAME— read the next section carefully
You set everything up, run ssh my-vm, and get:
Permission denied (publickey).
The key is correct. The tunnel works. But the username is wrong.
GCP has two SSH authentication modes, and each uses a different username format:
The VM has a ~/.ssh/authorized_keys file. Your username is whatever gcloud compute ssh created on the VM — usually your local macOS/Linux username.
User shubhamrasal
Google manages keys centrally. Your username is derived from your Google account email — @ and . become _.
# shubham@projectdiscovery.io becomes:
User shubham_projectdiscovery_io
Quick way — let gcloud tell you:
gcloud compute ssh VM_NAME --zone=ZONE --project=PROJECT_ID --tunnel-through-iap --dry-runLook for username@ in the output. That's your User.
Or check which mode is active:
# Project-level setting
gcloud compute project-info describe --format='value(commonInstanceMetadata.items.enable-oslogin)'
# Instance-level override
gcloud compute instances describe VM_NAME --zone=ZONE --format='value(metadata.items.enable-oslogin)'TRUE→ OS Login. Get username:gcloud compute os-login describe-profile(look forusernamefield)- Empty or
FALSE→ Traditional. SSH in once with gcloud, runwhoami
gcloud compute ssh auto-generates a key pair at ~/.ssh/google_compute_engine on first use. If this file doesn't exist:
# This creates the key and registers it
gcloud compute ssh VM_NAME --zone=ZONE --project=PROJECT_ID --tunnel-through-iapFor OS Login, ensure the key is registered:
gcloud compute os-login ssh-keys add --key-file=~/.ssh/google_compute_engine.pub --project=PROJECT_IDssh -v my-vm 2>&1 | grep -E "Offering|identity|Authentication"You'll see something like:
debug1: Offering public key: ~/.ssh/google_compute_engine RSA SHA256:xxxx explicit
debug1: Authentications that can continue: publickey
If the key is offered but rejected:
- Wrong username (most common) — see the username section above
- Key not registered — for OS Login:
gcloud compute os-login describe-profileto verify. For traditional: check~/.ssh/authorized_keyson the VM - Wrong key file — compare fingerprints:
# Local key fingerprint ssh-keygen -lf ~/.ssh/google_compute_engine.pub # What's registered in OS Login gcloud compute os-login describe-profile
# Production VM — traditional SSH keys, behind IAP
Host prod-server
ProxyCommand gcloud compute start-iap-tunnel prod-vm %p --listen-on-stdin --zone=us-east1-b --project=my-company-prod
IdentityFile ~/.ssh/google_compute_engine
User shubhamrasal
UserKnownHostsFile ~/.ssh/google_compute_known_hosts
IdentitiesOnly yes
CheckHostIP no
# Dev VM — OS Login enabled, behind IAP
Host dev-server
ProxyCommand gcloud compute start-iap-tunnel dev-vm %p --listen-on-stdin --zone=us-central1-a --project=my-company-dev
IdentityFile ~/.ssh/google_compute_engine
User shubham_company_io
UserKnownHostsFile ~/.ssh/google_compute_known_hosts
IdentitiesOnly yes
CheckHostIP noNotice: prod-server uses plain username (traditional mode), dev-server uses email-derived username (OS Login). They can coexist.
# Direct SSH
ssh prod-server
# SCP
scp ./deploy.sh prod-server:/tmp/
# Rsync
rsync -avz ./app/ prod-server:/opt/app/
# VSCode Remote-SSH — just pick "prod-server" from the host list
# Ansible inventory
# [production]
# prod-servergcloudCLI installed and authenticated (gcloud auth login)- IAP TCP forwarding enabled (docs)
roles/iap.tunnelResourceAccessorIAM role on the VM or project~/.ssh/google_compute_enginekey pair (auto-created on firstgcloud compute ssh)