Most on-premises data migrations stall at the same point: the network team says no.
The database sits in a data centre with no public IP, and it is not going to get one. There is no VPN to the cloud, and building one is a quarter-long project across three teams. You still need the table in Snowflake this month.
Snowflake's Data Connectivity Proxy (DCP) is built for this situation. It is one of several ways to reach a private source, so it helps to know exactly which problem it solves better than the others.
The options, and where each one breaks
| Path | Inbound firewall change | Who runs the data plane | Fits when |
|---|---|---|---|
| Public endpoint + IP allowlist | Yes | Snowflake | the source is already internet-facing |
| VPN or Direct Connect + PrivateLink | A network project | Snowflake | it already exists and many workloads share it |
| Openflow BYOC / self-hosted deployment | No | You | you want the compute inside your own perimeter |
| Push model: an ETL tool writes into Snowflake | No | You | you accept operating an ETL stack |
| DCP | No | Snowflake | no inbound change is possible, and you want Snowflake to run the data plane |
DCP is the only option here that combines a Snowflake-managed data plane, zero inbound exposure, and no VPN. If you already have PrivateLink, use it. If you are happy to run the data plane yourself, BYOC works. DCP makes sense when neither of those is available.
What DCP is
You run a small agent on your network. It opens one outbound TLS connection to Snowflake on port 443 and keeps it open, and Snowflake sends database traffic back through that tunnel.
The agent passes TCP traffic through. It does not parse, buffer, or store the payload, and the database credentials stay in Snowflake.
Architecture: two planes, one direction
The control plane (dcp.<account>...) handles bootstrap, issues and rotates certificates, and pushes the routing policy to the agent. The data plane (dcp-proxy.<account>..., plus a fallback host) carries the database bytes.
Allow all three hostnames. If you allow only the fallback host, the agent still starts and data still flows, but every connection uses the degraded path and nothing warns you.
The trust boundary
A security reviewer will read this section closely:
- The agent is outbound only. It opens no listening socket for data.
- It passes bytes through without parsing, buffering, or storing them.
- Credentials stay in Snowflake, in a secret or a connector parameter. The agent host never holds the database password.
- Transport uses mutual TLS with a client certificate that the Snowflake control plane issues. Operational certificates rotate about every 30 days, independently of the bootstrap token.
The agent host does carry two risks. It holds a bootstrap JWT that is valid for up to 90 days, and anyone with that file can register an agent against your account until it expires, so rotate it like any other secret. The agent's Prometheus endpoint has no authentication and shows exactly when your credential expires, so bind it to loopback and scrape it locally.
Routing follows the destination
The connector has no tunnel setting and no proxy field. You configure it as if the database were directly reachable, and DCP does not appear anywhere in the connector configuration.
Snowflake never resolves db.internal. The relay matches the hostname as a string against a network rule, and the agent resolves it on your network, where the name exists.
Most of the setup rules follow from this. A hostname with no public DNS record works. A raw IP address is rejected, because the agent has nothing to resolve. You never point a connector at a tunnel: you name a destination, and the relay finds the agent that owns it.
What to ask the network team for
This is the request:
One process on one host, making outbound connections to four fixed hostnames on TCP 443. No inbound rules. No listening ports. No public IP on the database. No VPN.
Add two caveats to the same request, because missing either one can waste a deployment window:
- The agent needs a direct-egress exception for a handful of FQDNs on 443, because it must reach the DCP hostnames itself. Everything else on the host can keep using the corporate proxy.
- That path cannot have TLS inspection. DCP authenticates with mutual TLS using a client certificate that Snowflake issues. A proxy that terminates TLS and re-signs with an internal CA cannot reproduce that certificate, so the handshake fails. A transparent pass-through proxy works; a decrypting one does not.
People miss the second point most often. The failure is intended: mutual TLS is doing its job.
Setup outline
There are five steps, in this order. Below are syntax skeletons only.
SELECT SYSTEM$ISSUE_PER_ACCOUNT_CERTIFICATES(); -- once per account, then wait about 30 minutes
CREATE DATA CONNECTIVITY PROXY <name> ENABLED = TRUE;
SELECT SYSTEM$GENERATE_DATA_CONNECTIVITY_PROXY_BOOTSTRAP_TOKEN('<name>', <days>);
CREATE NETWORK RULE <db>.<schema>.<rule>
MODE = DATA_CONNECTIVITY_PROXY_EGRESS -- plain EGRESS is silently ignored
TYPE = HOST_PORT
VALUE_LIST = ('<host>:<port>'); -- hostname, never an IP
CREATE EXTERNAL ACCESS INTEGRATION <eai>
ALLOWED_NETWORK_RULES = (<rule>) ENABLED = TRUE;
ALTER DATA CONNECTIVITY PROXY <name>
SET EXTERNAL_ACCESS_INTEGRATIONS = (<eai>); -- the step everyone skips
Then, spin up the docker image for the dcp-client
docker run -d snowflakedb/dcp-client:latest \
--sf-bootstrap-credentials <token-path> \
--metrics-addr 127.0.0.1:9092
Before you touch the connector, run the route check:
SELECT * FROM TABLE(INFORMATION_SCHEMA.DCP_CLIENT_ROUTE_CHECK(
AGENT_ID => '<name>', DESTINATION => '<host>:<port>'));
These four traps cost me time:
- The certificate call is asynchronous. If you start the agent inside the 30-minute window, it cannot get its mTLS pair, and the error does not say why.
- Only
ALTER DATA CONNECTIVITY PROXY ... SET EXTERNAL_ACCESS_INTEGRATIONSassociates an integration with the proxy. Creating the integration does not do it, and neither does attaching it to the Openflow runtime. You still need that attachment as a separate step. - Snowflake accepts
MODE = EGRESSbut does not apply it to DCP. It looks like a routing bug. - Never use
CREATE OR REPLACEon a network rule or an external access integration. It silently detaches the object from every runtime that references it, and unrelated connectors break with no error.
Limitations
DCP is new, and its limits are firm. Learn them before you design around it.
| Limitation | What it means for you |
|---|---|
| Serves Openflow only today | Other Snowflake services are planned, not available. The consumer is always an Openflow connector. |
| No corporate forward HTTP proxy | The agent needs direct egress. There is no proxy configuration to set. |
| No PrivateLink for the agent | The agent must reach the public DCP hostnames. Your account can still use PrivateLink for inbound. |
| No active-active load balancing | Two agents cannot share load to the same destination. Split destinations across agents instead. |
| Agent upgrades are not hitless | Replacing the image tears down active TCP sessions. It drains on SIGTERM, so allow a graceful stop. Relay-side upgrades are transparent. |
| Connection resets are visible | If an agent fails, its tunnels break. DCP does not hide this — the consumer needs reconnect logic. |
| One proxy object per network scope | Sources in isolated networks each need their own agent and their own DCP object. |
Observability is limited. The per-stage route check is the best single diagnostic, but only its first six stages tell you anything. I saw the last two stay at IN_PROGRESS while the connector was live and counting connections, and the connection-history table function returned one placeholder row the whole time. The agent's own OTLP metrics (agent_cp_connected, agent_connections_total, agent_destination_errors_total) are the signal I trust.
Cost and latency
DCP has no billable compute object of its own. You pay for the Openflow runtime and for the warehouse that applies changes.
For a CDC pipeline, the split matters. Capture runs continuously, writes to a journal table, and uses no warehouse. Only the scheduled merge spends warehouse credits. When there is nothing to merge, the
warehouse suspends and costs nothing.
Latency surprised me. Round-trip time to the relay barely matters, because CDC keeps connections open for a long time, so you pay the TLS setup cost once. Your merge schedule sets data freshness, so tune it first. RTT matters for cold starts and throughput.
The agent is I/O-bound. Host network bandwidth limits throughput before encryption does. To scale, add agents and split destinations across integrations; a bigger host will not help much.
When not to use DCP
- A one-off ad-hoc query against an on-prem database. DCP is for pipelines.
- Any consumer other than Openflow, for now.
- A destination with no DNS name and no way to add a hosts entry on the agent host.
- An environment that requires TLS inspection on all egress with no exception path. There is no workaround for that.
Should you use it?
If the blocker is organisational (the network team will not open inbound, and a VPN is a quarter away), DCP turns a network project into an outbound exception for four hostnames. Run at least two agents in separate failure domains and alert on bootstrap-token expiry, and it holds up as a production pattern. Keep the limitations table open while you design.
Verified on Snowflake as of September 2026, on an Openflow gen 1 runtime replicating PostgreSQL. The feature is evolving, so check the current documentation for what has shifted.


Top comments (0)