Getting on-premises SQL Server data into Databricks: the networking is the hard part

snicholson
New Contributor II

Most of the guidance on getting SQL Server data into Databricks quietly assumes the SQL Server is already in Azure. If it is, the managed connector story is easy. But on a lot of real projects the database is sitting on-premises, in a data centre that was there long before anyone drew up a cloud strategy, and that one fact changes the whole job.

I have been through this on client work, and the thing I keep relearning is that the ingestion tool is rarely what makes it hard. The networking underneath it is. So this is about why that is, and about a split that has worked well for me: classic compute for the part that needs tight control of the network path, serverless for the part that does not.

Where it stops being easy

Lakeflow Connect's managed SQL Server connector is genuinely good. You get incremental ingestion using change tracking and change data capture, it is governed by Unity Catalog, and for a database source it runs an ingestion gateway and an ingestion pipeline on serverless compute. If your source is an Azure SQL Database, an Azure SQL Managed Instance, or SQL Server on an Azure VM, you point it at the database and it works, because the source is already inside Azure and private connectivity is straightforward.

On-premises is where it gets more involved. The connector does support on-premises SQL Server, but the documented path needs a private network bridge from your data centre into Azure over ExpressRoute. So the connector itself is not really the problem. The problem is getting a private, controlled network path between the compute that reads the database and a database that lives behind your own corporate firewall.

The piece that makes this concrete is the ingestion gateway. That gateway is the component that actually connects to your SQL Server and pulls the changes, which means it needs a direct network line of sight to the source. Everything else in the design follows from that one requirement. If the gateway cannot see the database over a private path, nothing downstream matters. So the real question is where you run that gateway and what network it lives in, and that is a networking decision, not an ingestion one.

What VNet injection is, and why it matters

Out of the box, Databricks classic compute runs in a network that Databricks manages for you. VNet injection lets you deploy that classic compute into a virtual network in your own Azure subscription instead. Your clusters run in your VNet, with your subnets, your routing and your network security groups.

For on-premises ingestion, that control is the whole point. When the compute sits in your own VNet, you can peer it with the network carrying your ExpressRoute connection back to the data centre, which is how the gateway gets its line of sight to the source. You can place private endpoints where you need them, control the routing, and give a security team the visibility they want over every hop between their database and the cloud. It is all happening inside a network you own, which is usually exactly what a regulated or cautious enterprise insists on before any of their data leaves the building.

Public and private subnets, briefly

VNet injection needs two dedicated subnets, and it is worth understanding why, because their names are slightly misleading. Databricks calls them the public (or host, or container) subnet and the private subnet, but neither is public in the sense of being open to the internet. The split is about how the two halves of a cluster node talk to things. Each node has a host side that handles communication back to the Databricks control plane, and that lives in the public subnet. The container side, where your actual workload runs, lives in the private subnet with no direct inbound access. Both subnets are yours, inside your VNet, governed by your network security groups. The naming is a historical quirk more than a description, and knowing that saves a lot of confusion the first time you set it up.

Serverless is the other end of the trade. It runs in a Databricks-managed network, which is what makes it so easy to use, and it is also why it cannot reach your private resources by default. For serverless, the way across is a Network Connectivity Configuration, an account-level regional object that manages private endpoints from the serverless plane out to your Azure resources. NCCs are good and they are clearly where things are heading, but there are limits worth knowing before you commit a delivery to them. NCC private endpoints have so far been geared around serverless SQL warehouses rather than every serverless workload, and the model assumes your target is reachable as an Azure resource in the first place. For a database still physically sitting on-premises, you are leaning on that ExpressRoute bridge either way.

So when I need full, auditable control of the path to an on-premises source, classic compute with VNet injection still gives me that more directly than serverless does today.

The bit that catches people out: subnet sizing is permanent

This is the detail that has caused more quiet pain than anything else, and it is worth slowing down on, because you cannot undo it.

You pick the address ranges for those two subnets as CIDR blocks, and the size of the ranges sets a ceiling on how many cluster nodes can run at once, because every node takes addresses from them. A rough way to size it: each node uses a couple of IPs, so work out the most nodes you expect to run concurrently across every cluster in that workspace at peak, double it for headroom, and size the ranges for that. It is far better to over-provision address space now than to discover the ceiling in production.

The reason to get it right the first time is that you cannot resize a subnet once it has resources deployed in it. Once your workspace is live and running production workloads on those subnets, the ranges are fixed in practice. If you sized them too small, there is no setting to tweak. You rebuild, and rebuilding networking under a live workspace is the kind of change nobody wants to put their name to.

So the sizing has to be worked out before you deploy, with the networking team in the room, because the ranges also have to fit inside the wider VNet, avoid colliding with anything already peered, and leave the private subnet enough room. It is the same thing I run into all over platform work. The code that stands the infrastructure up is the small part. The decisions you make with the networking team before any code runs are the ones you are stuck with for years. Get the CIDR sizing wrong and it is not a bug you fix, it is a migration you schedule.

There is a recent change that makes this even more worth getting right. Since the end of March 2026, new Azure VNets default to private with no outbound internet access, so a freshly created VNet needs an explicit way out, such as a NAT gateway, before your clusters can reach anything. That is one more thing to design up front rather than trip over on deployment day.

The split that has worked for me

Put all that together and the pattern I keep landing on for on-premises sources is not classic versus serverless. It is classic for the part that needs network control, serverless for the part that does not.

Do the ingestion on classic compute in a VNet-injected workspace. This is the stage where the gateway has to reach across ExpressRoute into your data centre and read from a database the security team watches closely. With the compute in your own VNet, and your peering and private endpoints around it, you get the direct, auditable control that stage needs, and the sensitive part of the path stays inside infrastructure you own.

Do the processing on serverless. Once the raw data has landed in the lakehouse, the transformation and modelling work does not need a private line back to the data centre any more. It needs compute that is elastic, cheap and quick to start, which is what serverless is good at. You get the simplicity and the scale-to-nothing cost profile, and you have not compromised the ingestion path to get it, because the part that touched the private source is already done.

What you end up with is each tool doing the thing it is actually best at. Classic compute is worth its extra setup where control is not negotiable. Serverless takes over everywhere control is not the thing you are worried about. The line between them is just the point where the data crosses out of your network and into your governed lakehouse.

Make it repeatable with Terraform

The last thing I would say is that none of this should live in someone's memory of which boxes they clicked. All of it, the VNet, the two subnets and their carefully chosen CIDR ranges, the NSGs, the peering to the ExpressRoute network, the private endpoints, the NAT gateway, and the workspace itself, is infrastructure you want defined in Terraform.

There are two good reasons beyond the usual ones. First, the subnet sizing you agonised over with the networking team is now written down and reviewable, so the next workspace starts from a known-good pattern instead of a fresh guess. Second, because you do this across clients, a Terraform module turns a fiddly, error-prone setup into something you can stand up consistently and audit. I run tflint and tfsec on every pull request so networking mistakes, an overly permissive rule or a wrong range, get caught in review rather than after a workspace is live. Given that you cannot resize those subnets later, having the sizing decision captured in code and checked before it deploys is worth a great deal.

The connector will happily be configured by hand once. The networking that makes it possible is the part you want reproducible, because that is the part that is expensive to get wrong and painful to change.

The one thing to take away

If I could tell someone one thing before they start an on-premises ingestion project on Databricks, it would be to spend the first effort on the network design, not the connector. Work out where the gateway runs and how it reaches the source. Size your subnets with the networking team as if you can never change them, because you more or less cannot. Pick classic or serverless per stage based on whether that stage genuinely needs control of the network. And put the whole thing in Terraform so the next one is repeatable. The ingestion tooling is mature and it will do its job. Whether the project is smooth or miserable comes down almost entirely to the networking calls you make before you deploy.

How are you handling on-premises sources into Databricks? Are you moving everything onto serverless with NCCs yet, or still reaching for classic compute and VNet injection when you need full control of the path?