The Problem

You've got Azure Databricks and Snowflake in the same region. Your security team mandates no public internet egress from the compute plane. You set up Private Link. You create the connection in Databricks. You run a federated query.

It fails.

Not with a permission error. Not with a schema mismatch. With a DNS resolution failure — NXDOMAIN — on a hostname that should resolve.

The root cause: you used the same hostname in two places that require different ones.

This article walks through the exact network topology, the DDL, and the failure mode so you can set this up in one pass instead of three.

The Architecture in One Diagram

┌─────────────────────────────────────────────────────────────────────────┐ │ Your Azure VNet │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ Azure Databricks (two planes) │ │ │ │ │ │ │ │ ┌────────────────────┐ ┌────────────────────────────────┐ │ │ │ │ │ Control Plane │ │ Compute Plane (Serverless / │ │ │ │ │ │ (managed by MSFT) │ │ Classic) │ │ │ │ │ │ │ │ │ │ │ │ │ │ • OAuth token │ │ • Runtime query execution │ │ │ │ │ │ exchange │ │ • Iceberg metadata reads │ │ │ │ │ │ • User browser │ │ • Data file reads │ │ │ │ │ │ redirect │ │ │ │ │ │ │ │ │ │ Uses: │ │ │ │ │ │ Uses: │ │ <account>.privatelink. │ │ │ │ │ │ <account>. │ │ snowflakecomputing.com │ │ │ │ │ │ snowflakecomputing│ │ (resolves to private IP via │ │ │ │ │ │ .com │ │ Private DNS zone) │ │ │ │ │ │ (PUBLIC hostname) │ │ │ │ │ │ │ └─────────┬──────────┘ └──────────────┬────────────────┘ │ │ │ └────────────┼────────────────────────────────┼────────────────────┘ │ │ │ │ │ │ │ (public internet) │ (Private Link) │ │ │ │ │ └───────────────┼────────────────────────────────┼─────────────────────────┘ │ │ │ TCP 443 │ TCP 443 │ │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ Snowflake (Azure region) │ │ │ │ Network Policy: │ │ ALLOW: AZURELINKID = '<your-private-endpoint-link-id>' │ │ ALLOW: IPV4 = '<databricks-control-plane-CIDRs>' │ │ BLOCK: IPV4 = '0.0.0.0/0' │ │ │ └─────────────────────────────────────────────────────────────────────────┘ 

The critical insight: Two different components of Databricks reach Snowflake over two different paths:

Component Path Hostname to use
Compute plane (query execution) Private Link <account>.privatelink.snowflakecomputing.com
Control plane + user browser (OAuth) Public internet <account>.snowflakecomputing.com

Why Two Hostnames?

The CREATE CONNECTION DDL in Databricks has two hostname fields:

  1. host — used at runtime by the serverless or classic compute plane to execute queries. This traffic flows over Private Link. It must use the .privatelink hostname because that's what resolves to the private IP in your Private DNS zone.

  2. OAUTH_AUTHORIZE_URL (or the equivalent in the UI) — hit by the user's browser and the Databricks control plane during the initial OAuth authorization flow. Neither of these has Private Link connectivity. It must use the regular public hostname.

The trap: If you put the .privatelink hostname in the authorize URL, the user's browser gets NXDOMAIN (the private hostname doesn't exist in public DNS). If you put the public hostname in the host field, the compute plane tries to route to a public IP and your network policy blocks it.

Both failures look like "connection refused" or "timeout" in the Databricks UI. The logs don't explicitly say "wrong hostname." You have to know to check.

Step-by-Step Setup

Prerequisites

  • Azure Databricks workspace with Unity Catalog enabled (auto-enabled for workspaces created after Nov 8, 2023)
  • Databricks SQL Warehouse (Pro or Serverless) or classic cluster on DBR 13.3 LTS+
  • Snowflake account on Azure with ACCOUNTADMIN or SECURITYADMIN access
  • Azure Private Endpoint created in your VNet pointing to the Snowflake Private Link service
  • Databricks Network Connectivity Configuration (NCC) with the Snowflake private endpoint rule added

Step 1: Create the OAuth Application in Snowflake

USE ROLE SECURITYADMIN; CREATE APPLICATION ROLE DBRX_FED_ROLE; CREATE APPLICATION DBRX_FED_APP ( TYPE = OAUTH, SCOPE = ( 'refresh_token', 'session:role:DBRX_FED_ROLE' ), RESPONSE_TYPE = 'CODE', REDIRECT_URI = 'https://<databricks-workspace-url>/oauth/callback' ); -- Generate client ID and secret SELECT SYSTEM$GENERATE_OAUTH_CLIENT_SECRET('DBRX_FED_APP'); 

Note the REDIRECT_URI — this must match your Databricks workspace URL exactly.

Step 2: Get the Private Link Endpoint LinkID

In Snowflake:

USE ROLE ACCOUNTADMIN; SELECT value:linkIdentifier, REGEXP_SUBSTR(value:endpointId, '([^/]+$)') AS short_id FROM TABLE( FLATTEN( INPUT => PARSE_JSON(SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS()) ) ); 

This returns the LinkID — a Snowflake-internal identifier for your Azure Private Endpoint. You'll need it for the network rule.

Step 3: Create Network Rules in Snowflake

USE ROLE SECURITYADMIN; -- Rule 1: Allow Private Link traffic from Databricks compute plane CREATE OR REPLACE NETWORK RULE allow_databricks_private_link MODE = INGRESS TYPE = AZURELINKID VALUE_LIST = ('<your-private-endpoint-link-id>'); -- Rule 2: Allow Databricks control plane IPs (for OAuth token exchange) CREATE OR REPLACE NETWORK RULE allow_databricks_cp MODE = INGRESS TYPE = IPV4 VALUE_LIST = ('<control-plane-CIDR-1>', '<control-plane-CIDR-2>'); -- Rule 3: Block ALL other public IP access CREATE OR REPLACE NETWORK RULE block_public_access MODE = INGRESS TYPE = IPV4 VALUE_LIST = ('0.0.0.0/0'); 

Critical gotcha: AZURELINKID rules and IPV4 rules operate on completely separate evaluation dimensions. A policy that only allows an AZURELINKID does NOT implicitly block public IP traffic. You must explicitly block with 0.0.0.0/0 to enforce Private-Link-only access.

Conversely, IPV4 rules never match Private Link traffic. A request arriving via Private Link is identified by its LinkID, not its source IP.

Step 4: Create and Attach the Network Policy

CREATE OR REPLACE NETWORK POLICY sf_databricks_fed_policy ALLOWED_NETWORK_RULE_LIST = ('allow_databricks_private_link', 'allow_databricks_cp') BLOCKED_NETWORK_RULE_LIST = ('block_public_access'); -- Attach to the security integration ALTER SECURITY INTEGRATION DBRX_FED_SF_NATIVE_OAUTH SET NETWORK_POLICY = 'sf_databricks_fed_policy'; 

If your analysts also need to access Snowflake from corporate IPs (for the browser-based OAuth flow), create a separate user-level policy:

CREATE OR REPLACE NETWORK RULE allow_corporate_ips MODE = INGRESS TYPE = IPV4 VALUE_LIST = ('<corporate-CIDR-1>', '<corporate-CIDR-2>'); CREATE OR REPLACE NETWORK POLICY user_browser_policy ALLOWED_NETWORK_RULE_LIST = ('allow_corporate_ips', 'allow_databricks_private_link'); ALTER USER <username> SET NETWORK_POLICY = 'user_browser_policy'; 

Step 5: Create the Connection in Databricks

This is where the two hostnames live. In a Databricks notebook or SQL editor:

CREATE OR REPLACE CONNECTION sf_fed_conn TYPE snowflake OPTIONS ( -- ⚠️ PRIVATE LINK hostname — used by compute plane at runtime host '<account>.privatelink.snowflakecomputing.com', port '443', -- OAuth credentials from Step 1 client_id '<oauth-client-id>', client_secret '<oauth-client-secret>', user '<snowflake-username>', -- ⚠️ PUBLIC hostname — used by browser/control plane for OAuth oauth_authorize_url 'https://<account>.snowflakecomputing.com/oauth/authorize', oauth_token_url 'https://<account>.snowflakecomputing.com/oauth/token', scope 'refresh_token session:role:DBRX_FED_ROLE' ); 

If you get NXDOMAIN in the Databricks driver logs: You used the .privatelink hostname in the oauth_authorize_url. Fix: use the public hostname there.

If you get a network policy rejection from Snowflake: You used the public hostname in the host field. Fix: use the .privatelink hostname there.

Step 6: Create the Foreign Catalog (Catalog Federation)

For catalog federation (Databricks reads Snowflake Iceberg tables directly from storage, more cost-effective than query federation):

-- First: create external location in Unity Catalog pointing to Snowflake's storage CREATE EXTERNAL LOCATION sf_iceberg_storage URL 'abfss://<container>@<storage-account>.dfs.core.windows.net/snowflake-iceberg/' CREDENTIAL (SCOPE 'AzureADSpn'); GRANT READ FILES ON EXTERNAL LOCATION sf_iceberg_storage TO METASTORE; -- Then: create the foreign catalog CREATE FOREIGN CATALOG sf_fed_catalog OPTIONS ( CATALOG 'snowflake_db', CONNECTION 'sf_fed_conn', AUTHORIZED_PATHS 'abfss://<container>@<storage-account>.dfs.core.windows.net/snowflake-iceberg/' ); 

Now you can query:

SELECT * FROM sf_fed_catalog.<schema>.<table> LIMIT 10; 

The Reverse Direction: Snowflake → Databricks Unity Catalog

If you also need Snowflake to read Databricks Unity Catalog tables (the other direction of the federation), the private connectivity setup is different:

In Snowflake:

USE ROLE ACCOUNTADMIN; -- Provision the private endpoint (one per region, not per catalog) SELECT SYSTEM$PROVISION_PRIVATELINK_ENDPOINT( '<databricks-vpc-endpoint-service-id>', '<databricks-workspace-hostname>' ); -- Create the catalog integration CREATE OR REPLACE CATALOG INTEGRATION unity_dbx_catalog_int CATALOG_SOURCE = ICEBERG_REST TABLE_FORMAT = ICEBERG CATALOG_NAMESPACE = '<uc-schema>' REST_CONFIG = ( CATALOG_API_TYPE = PRIVATE, -- ⚠️ Routes via Private Link CATALOG_URI = 'https://<databricks-workspace-url>/api/2.1/unity-catalog/iceberg-rest', CATALOG_NAME = '<uc-catalog-name>', ACCESS_DELEGATION_MODE = VENDED_CREDENTIALS ) REST_AUTHENTICATION = ( TYPE = OAUTH OAUTH_TOKEN_URI = 'https://<databricks-workspace-url>/oidc/v1/token' OAUTH_CLIENT_ID = '<sp-client-id>' OAUTH_CLIENT_SECRET = '<sp-client-secret>' OAUTH_ALLOWED_SCOPES = ('all-apis', 'sql') ) ENABLED = TRUE; 

Note the pattern repeats: CATALOG_URI uses the public Databricks workspace URL, but CATALOG_API_TYPE = PRIVATE tells Snowflake to route the actual traffic through the provisioned Private Link endpoint. You don't put the private endpoint name in the URI — Snowflake handles the routing internally.

As of April 2026, Snowflake supports write operations (INSERT, UPDATE, DELETE) against Unity Catalog tables on Azure (GA). So this is no longer read-only.

Debugging Checklist

Symptom Likely Cause Fix
NXDOMAIN in Databricks driver logs .privatelink hostname in oauth_authorize_url Use public hostname for OAuth URLs
Snowflake returns 403 / network policy rejection Public hostname in host field, or missing AZURELINKID rule Use .privatelink hostname in host; verify LinkID rule exists
OAuth redirect fails in browser User's corporate IP not in network policy Add corporate CIDR to user-level network policy
CATALOG_API_TYPE error in Snowflake Forgot to set PRIVATE Add CATALOG_API_TYPE = PRIVATE to REST_CONFIG
Vended credentials fail (403 from ADLS) External volume / storage credential misconfigured in Snowflake Verify EXTERNAL_VOLUME or VENDED_CREDENTIALS mode; check ADLS RBAC
Works in dev, fails in prod Different Private Endpoint (different LinkID) Re-run SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS in the prod account

What I'd Do Differently

  1. Template the network policy as Terraform/Bicep. The AZURELINKID + IPV4 + 0.0.0.0/0 block triple is easy to get wrong in manual SQL. Codify it.

  2. Don't use 0.0.0.0/0 block in the initial setup. Get the happy path working first (public internet), verify the federation, then tighten to Private-Link-only. Debugging a network policy rejection while also debugging an OAuth misconfiguration is a miserable experience.

  3. Use REFRESH_INTERVAL_SECONDS on the Snowflake catalog integration if your Databricks tables change frequently. Without it, Snowflake caches metadata and you'll see stale table lists.

  4. Tag the Databricks connection with environment labels. When you have dev/staging/prod Snowflake accounts, the .privatelink hostname changes per account. A naming convention (sf_fed_conn_prod) saves you from cross-wiring.

TL;DR

  • host in CREATE CONNECTION → .privatelink.snowflakecomputing.com (compute plane, Private Link)
  • oauth_authorize_url → regular snowflakecomputing.com (browser + control plane, public)
  • Network policy must allow AZURELINKID AND IPV4 (control plane) AND explicitly block 0.0.0.0/0
  • These are separate evaluation dimensions — one does not imply the other
  • For the reverse direction (Snowflake → Databricks), use CATALOG_API_TYPE = PRIVATE but keep the public workspace URL in CATALOG_URI

If this saved you an afternoon of DNS debugging, a clap on dev.to helps. If you hit a failure mode not covered here, drop it in the comments.