This post describes how to connect to a GCP VM instance via SSH behind a firewall. There are two options: SSH over HTTPS or SSH over IAP. The first is the easiest to set up. The second is more secure, but takes more steps.
If you want the short answer: use Method 2 (IAP). Moving sshd to port 443 is a firewall workaround rather than a security measure, whereas IAP TCP forwarding plus OS Login is what Google documents as the recommended way to reach VMs — including VMs with no external IP at all. Method 1 is covered anyway because it still works, and is occasionally the only thing that gets through a hostile corporate proxy.
Method 1: SSH over HTTPS
In this method, we will explain how you can make a SSH connection over the HTTPS port. Most firewall rules should allow this, but some proxy servers may interfere.

Firstly, within your VM instance (either SSH via the web browser from the Compute Engine -> VM Instances section or a terminal within a notebook) we need to append the /etc/ssh/sshd_config file to change the default SSH port from 22 to 443. We can do this directly by running:
sudo sed -i -e "/#Port /c\Port 443" /etc/ssh/sshd_config
After this, you need to restart the VM instance, or at the very least restart the SSH service on the VM by running:
sudo service ssh restart
sudo service sshd restart
For the next steps, we need to modify the firewall rules in order to allow SSH via HTTPS. We can do this directly within the VM instance settings within Compute Engine, but here we will do this via command line using the Google Cloud CLI.
You can install gcloud CLI via the instructions here, but if you use vscode then a better way would be to install the Cloud Code extension which comes with the Google Cloud SDK and gcloud. After this has been completed, you can set up the current project by running the following within a terminal window:
gcloud config set project PROJECT_ID
Then you can run the following command to add the neccessary firewall rules:
gcloud compute firewall-rules create allow-ssh-via-https \
--action=allow \
--rules=tcp:443 \
--source-ranges=YOUR.OFFICE.IP.RANGE/32 \
--target-tags=ssh-via-https
Do not omit
--source-ranges. Agcloud compute firewall-rules createwith no source range defaults to0.0.0.0/0— the whole internet can then reach sshd on your VM. Always scope the rule to the addresses you actually connect from, and use--target-tagsso it applies only to the VMs you tag, not to every instance in the network.
You may need to ask the project owner(s) to the add the role to add firewall rules to your account. You can see view the roles and permissions from:
gcloud projects get-iam-policy PROJECT_ID --format=json
You can start the VM instance by running:
gcloud compute instances start INSTANCE_NAME
Now we can generate the SSH keys and test the connection by running:
# Windows / PuTTY: the port flag is capital -P
gcloud compute ssh USERNAME@INSTANCE_NAME --project=PROJECT_ID --zone=ZONE -- -P 443
# macOS / Linux / OpenSSH: the port flag is lowercase -p
gcloud compute ssh USERNAME@INSTANCE_NAME --project=PROJECT_ID --zone=ZONE -- -p 443
Everything after the bare -- is passed straight through to the underlying SSH client, which is why the flag differs by platform. On Windows this should open a PuTTY window which will connect to the VM; elsewhere you stay in the same terminal. If successful, you should see the bash terminal appear from your VM.
In order to establish this connection directly (required for vscode), we can set this up in the SSH config automatically by running the command:
gcloud compute config-ssh
This will append the file~./ssh/config in your user directory (c:\users\USER\.ssh\config on windows) with the VM instances that are currently running or assigned a static IP adddress. You should see the following for each of the VMs within the project:
# Google Compute Engine Section
#
# The following has been auto-generated by "gcloud compute config-ssh"
# to make accessing your Google Compute Engine virtual machines easier.
#
# To remove this blob, run:
#
# gcloud compute config-ssh --remove
#
# You can also manually remove this blob by deleting everything from
# here until the comment that contains the string "End of Google Compute
# Engine Section".
#
# You should not hand-edit this section, unless you are deleting it.
#
Host INSTANCE_NAME.ZONE.PROJECT_ID
HostName INSTANCE_IP
IdentityFile C:\Users\USER\.ssh\google_compute_engine
UserKnownHostsFile=C:\Users\USER\.ssh\google_compute_known_hosts
HostKeyAlias=compute.xxxxxxxxxxx
IdentitiesOnly=yes
CheckHostIP=no
# End of Google Compute Engine Section
This will also be required if you want to connect to your VM instance via SSH in vscode via the Remote - SSH extension. Once this has been added, you will need to modify it so that it can be used with the right port (443) and username (as your local username is likely different to the one set on GCP), so the final config should look like:
Host INSTANCE_NAME.ZONE.PROJECT_ID
HostName INSTANCE_IP
Port 443
User USERNAME
IdentityFile C:\Users\USER\.ssh\google_compute_engine
UserKnownHostsFile=C:\Users\USER\.ssh\google_compute_known_hosts
HostKeyAlias=compute.xxxxxxxxxxx
IdentitiesOnly=yes
CheckHostIP=no
This should complete the set up, and you can connect to the VM instance by running:
ssh INSTANCE_NAME.ZONE.PROJECT_ID
or by using the Remote - SSH extension on vscode.
You may also need to configure your proxy settings to allow the connection to be made by running the following commands:
gcloud config set proxy/type http
gcloud config set proxy/address proxy.testcorp.com
gcloud config set proxy/port 8080
# optional:
gcloud config set proxy/username user001
gcloud config set proxy/password XXXXXXXXXXXX
Method 2: SSH over IAP
Identity-Aware Proxy (IAP) is a managed service that can control the access to your VM. It allows you to authenticate user TCP traffic through IAP before sending it to your VM instances. This also works for private VM’s without an external IP address.
tunnel.cloudproxy.app ──▶ GCP VM
Private / No external IP
In order to establish the connection over IAP, we need to add the firewall rules and user role to your account. This can be done directly via the webbrowser within the Compute Engine section, but here we will do this via the gcloud CLI.
Firstly, we need run the following command to set up the firewall rule to allow connection from IAP:
gcloud compute firewall-rules create allow-ssh-ingress-from-iap \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--target-tags=iap-ssh
The --source-ranges=35.235.240.0/20 flag is the important part. All IAP TCP forwarding traffic originates from that single Google-owned range, so this is the tightest possible ingress rule: nothing else on the internet can reach port 22 at all. Without the flag the rule defaults to 0.0.0.0/0, which gives you an internet-facing SSH port and defeats the entire point of using IAP. Tag the VMs you want reachable with iap-ssh so the rule does not blanket the whole network.
While you are here, delete the permissive defaults that ship with the default VPC:
gcloud compute firewall-rules delete default-allow-ssh default-allow-rdp
These allow connections from every address on the internet rather than only from IAP. For new VMs, also create them with --no-address so they have no external IP — IAP reaches them regardless, and there is then no public surface to attack.
You also need to add the role permission (iap.tunnelResourceAccessor) to your user account to allow for connection over IAP:
gcloud projects add-iam-policy-binding PROJECT_ID --member=user:USERNAME --role=roles/iap.tunnelResourceAccessor
There are two IAM grants involved and forgetting the second is the most common failure. roles/iap.tunnelResourceAccessor authorises the tunnel; roles/compute.osLogin (or roles/compute.osAdminLogin for sudo) authorises the login itself once the tunnel is up:
gcloud projects add-iam-policy-binding PROJECT_ID --member=user:USERNAME --role=roles/compute.osLogin
A 403 Permission denied when opening the tunnel means the first role is missing; an SSH key rejection after the tunnel connects means the second is.
This will require a certain level of access on the project, and if you get the following error:
ERROR: (gcloud.projects.add-iam-policy-binding) User [USER-EMAIL] does not have permission to access projects instance [PROJECT-NAME]
Then you may need to ask the project owner(s) to the add the role to your account. You can see view the roles and permissions from:
gcloud projects get-iam-policy PROJECT_ID --format=json
Before connecting, enable OS Login across the project:
gcloud compute project-info add-metadata --metadata enable-oslogin=TRUE
Google recommends pairing OS Login with IAP because it replaces static credentials with dynamic authorisation. With OS Login enabled, GCP checks the connecting user’s IAM permissions at connection time rather than looking up a key stored in instance metadata, so revoking someone’s IAM role cuts their access immediately — no key files to hunt down. Relatedly, avoid granting compute.instances.setMetadata broadly: that is the permission that lets a user push their own SSH key onto an instance and bypass the model.
After this has been completed, you need to establish the connection by running:
gcloud compute ssh USERNAME@INSTANCE_NAME --project=PROJECT_ID --zone=ZONE --tunnel-through-iap
(Watch for --zone=ZONE-- -P 22, a typo that circulates widely — the missing space breaks the flag, and port 22 is the default so the passthrough is unnecessary.)
For anything that is not an interactive shell — port forwarding, scp, a database client, VS Code Remote — use start-iap-tunnel directly:
# Forward a local port to a port on the VM through IAP
gcloud compute start-iap-tunnel INSTANCE_NAME 22 --local-host-port=localhost:2222 --zone=ZONE
# Then treat it as a local SSH target
ssh -p 2222 USERNAME@localhost
or as a ProxyCommand in ~/.ssh/config, which is what makes the VS Code Remote - SSH extension work against a VM with no external IP:
Host my-vm
HostName INSTANCE_NAME
User USERNAME
IdentityFile ~/.ssh/google_compute_engine
ProxyCommand gcloud compute start-iap-tunnel %h %p --listen-on-stdin --zone=ZONE --project=PROJECT_ID --quiet
One caveat worth knowing: IAP routes traffic through Google’s infrastructure, which adds latency. It is unnoticeable in an interactive session, but for large file transfers stage the data through Cloud Storage rather than pushing it down the tunnel.
IAP TCP tunnels data through the domain tunnel.cloudproxy.app. This domain is owned by Google. You should ensure you are not blocking any traffic to this domain. If you block traffic to this domain you will be unable to use IAP for TCP and you may see the error message:
Error while connecting [[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
In order to resolve this, you need to complete the following steps:
1) Ask your friendly proxy admins to add the following to allow list:
wss://tunnel.cloudproxy.app
2) Configure your current GCLOUD_SDK environment
gcloud config set proxy/type http
gcloud config set proxy/address proxy.testcorp.com
gcloud config set proxy/port 8080
# optional:
gcloud config set proxy/username user001
gcloud config set proxy/password XXXXXXXXXXXX
3) Make sure you have correct SSL Certs installed on your workstation. You can configure GCLOUD_SDK to use your certs using the following command:
gcloud config set custom_ca_certs_file /Users/user01/gce/certs/corpcerts.pem
If you have any issues with any of the methods or steps above, please see the troubleshooting ssh user guide. Also worth reading are the using IAP for TCP forwarding user guide and Google’s best practices for controlling SSH network access.
The 2026 short version
If you want the whole of Method 2 as a checklist:
- Create VMs with
--no-address— no external IP. - One ingress rule,
tcp:22from35.235.240.0/20, scoped by target tag. - Delete
default-allow-sshanddefault-allow-rdp. enable-oslogin=TRUEproject-wide.- Grant
roles/iap.tunnelResourceAccessorandroles/compute.osLogin. - Connect with
--tunnel-through-iap, orstart-iap-tunnelas aProxyCommandfor tooling. - Do not hand out
compute.instances.setMetadata.
That gives you SSH access with no public attack surface, authorisation checked per connection against IAM, and no long-lived keys sitting in instance metadata.