Most enterprises running SFTP today are running it themselves — a hardened VM, an open-source gateway, or a third-party SaaS bolted onto a bucket. Google’s answer is Cloud FTP: a managed SFTP front end for Cloud Storage, where users authenticate with SSH keys, get mapped to a service account, and read or write a bucket through standard SFTP clients like FileZilla, WinSCP, or Cyberduck — no VM to patch, no SSH daemon to harden.
The interesting design decision, and the one this post is actually about, is that Cloud FTP ships with two distinct server types — internal and external — and that split maps almost exactly onto the two audiences every enterprise needs to serve: employees who should never leave the corporate network, and outside vendors or customers who by definition live outside it. Getting the architecture right means keeping both audiences functional while a single VPC Service Controls perimeter still governs where the underlying data can go.
How Cloud FTP actually works
Before the network architecture, the access model, because everything downstream depends on it:
- Each SFTP user authenticates with an SSH public key — no passwords.
- Every user is mapped to a service account. You grant that service account whatever IAM roles it needs to read or write the target Cloud Storage bucket — nothing more.
- The Cloud FTP Service Agent generates short-lived tokens for the user’s service account at connection time and uses them to access the bucket. Your SFTP user never holds a long-lived bucket credential directly.
- The server’s own SSH host key is Google’s to run, not yours — unlike a self-hosted SFTP server, you never generate, store, or protect that key yourself. (This is separate from the per-user SSH public keys below, which you do generate and register yourself.)
- All traffic runs over an encrypted SSH channel, with built-in data integrity checks between client and server.
That indirection — user → service account → IAM-scoped bucket access — is what makes per-vendor and per-employee segmentation possible later in this post. It’s also, notably, exactly the identity model the rest of this site’s Google Cloud content keeps coming back to: don’t hand out credentials, hand out narrowly-scoped identities.
Two server types, two trust boundaries
| Internal server | External server | |
|---|---|---|
| Reachable from | Only your VPC network(s), via Private Service Connect | The public internet |
| Access control | You specify which projects are allowed to connect | You specify an IP address allowlist |
| Intended audience | Employees and internal systems already inside your network | External vendors, customers, and partners |
| Client-side requirement | Client must already be on an allowed VPC network | Client just needs a source IP inside the allowlist |
This isn’t an incidental feature list — it’s the whole architecture. An internal server assumes the client is already inside a network you trust, and uses Private Service Connect to keep the SFTP endpoint off the public internet entirely. An external server assumes the opposite: the client is outside your network by definition, so the only network-layer lever available is restricting which internet source IPs may connect at all.
Reference architecture
The pattern below puts both server types in a dedicated project, wraps that project in a VPC Service Controls perimeter, and keeps internal and vendor data on physically separate buckets so a misconfigured IAM binding on one path can’t leak into the other.
flowchart TB
subgraph Corp["Corporate Shared VPC"]
Emp[Employee SFTP clients]
end
subgraph Vendors["Public internet"]
V1[Vendor A]
V2[Vendor B]
end
subgraph Perimeter["VPC Service Controls perimeter — data-exchange-prod"]
subgraph IntSvr["Internal Cloud FTP server"]
PSC[Private Service Connect endpoint]
end
subgraph ExtSvr["External Cloud FTP server"]
AL[IP allowlist:<br/>Vendor A range, Vendor B range]
end
BucketInt[(Bucket: internal-exchange)]
BucketA[(Bucket: vendor-a-exchange)]
BucketB[(Bucket: vendor-b-exchange)]
end
Emp -->|PSC, project allowlist| PSC --> IntSvr --> BucketInt
V1 -->|SSH key + allowlisted IP| AL --> ExtSvr
V2 -->|SSH key + allowlisted IP| AL
ExtSvr -->|per-vendor service account| BucketA
ExtSvr -->|per-vendor service account| BucketB
Two servers, two network paths, one perimeter governing where the data underneath either path is allowed to end up.
Locking down the internal path
The internal server’s Private Service Connect model already does most of the work: there is no public IP to scan, brute-force, or accidentally leave open. The remaining decisions are about scope, not exposure:
- Keep the “allowed projects” list minimal. If you’re on Shared VPC (the model most enterprises actually run), only the host project and the specific service projects that legitimately need SFTP access should be on that list — not every project in the org.
- Don’t reuse one service account for every internal user. The mapping is per-user; use it. A finance analyst’s SFTP user shouldn’t share a service account — and therefore shouldn’t share bucket access — with a systems-integration pipeline.
- Treat the PSC endpoint’s DNS name as internal-only. Nothing about PSC prevents someone from documenting the endpoint in a wiki that’s more widely readable than the VPC it lives on. Keep the connection details as scoped as the access itself.
Locking down the external path
The external server is where most of the real design work sits, because “reachable from the public internet” is the starting condition, not something you’re eliminating:
- Scope the IP allowlist to each vendor’s actual known egress range, not a broad corporate CIDR you were handed once and never revisited. Ask vendors for their static egress IPs specifically — most enterprise vendors already have them for this exact reason.
- One SFTP user and one service account per vendor, minimum. Never let two external vendors share credentials or a service account, even temporarily during onboarding.
- One bucket (or bucket + IAM-conditioned prefix) per vendor, scoped with the narrowest role that satisfies the workflow — typically
roles/storage.objectViewerfor a vendor that only downloads, or a custom role limited tostorage.objects.createfor one that only drops files, rather than a blanketobjectAdmin. - Consider static outbound IPs plus a matching firewall allowlist as a second layer if the external server’s traffic transits infrastructure you control before reaching Cloud Storage — belt-and-suspenders is warranted here precisely because the network is public by design.
Where VPC Service Controls actually fits
This is the piece that’s easy to get backwards. VPC Service Controls does not replace the network-layer controls above — PSC and IP allowlisting are still what decide who can open an SFTP session at all. What the perimeter adds is a second, independent layer underneath: it governs the Google Cloud API calls the Cloud FTP control plane and the backend Cloud Storage access make, so that even a correctly-authenticated SFTP session can’t be leveraged — by a compromised credential, a misconfigured pipeline, or an insider — to move data to a bucket or project outside your trust boundary.
Put the project holding both Cloud FTP servers and both sets of buckets inside one perimeter. Since Cloud FTP’s VPC Service Controls support is currently Preview, treat it the way you’d treat any Preview security control: enable the perimeter in dry-run mode first, watch the violation logs for a full business cycle (vendor file drops are often weekly or monthly, not daily), and only flip to enforced once you’re confident nothing legitimate gets blocked.
Common mistakes
- Putting internal and vendor data in the same bucket “temporarily.” It never stays temporary, and it’s the single fastest way to turn a vendor IAM mistake into an internal data exposure.
- Sharing one service account across multiple vendors because provisioning a new one per vendor felt like overhead. It’s the overhead that keeps a compromised vendor credential from becoming everyone’s problem.
- Treating the external server’s IP allowlist as a one-time setup. Vendor egress ranges change when they move providers or add redundancy; stale allowlist entries either lock a vendor out or, worse, get replaced with something broad “just to unblock them.”
- Assuming VPC Service Controls stops an SFTP session by itself. It doesn’t — see above. Skipping the network-layer controls because “the perimeter will catch it” leaves the actual SFTP port with no gate at all.
- Enabling the perimeter in enforced mode on day one for a Preview integration. Dry-run first, always, on anything this new.
References
- Cloud FTP overview — Google Cloud Documentation
- Cloud FTP documentation home — Google Cloud Documentation
- gcloud alpha storage ftp — Google Cloud SDK reference
- Overview of VPC Service Controls — Google Cloud Documentation
- Private Service Connect — Google Cloud Documentation

