Edit

Use go-mssqldb with Azure SQL Database

The go-mssqldb driver supports connecting to Azure SQL Database, Azure SQL Managed Instance, and SQL database in Microsoft Fabric. This article covers Azure-specific configuration, authentication, connection limits, and troubleshooting that differs from on-premises SQL Server.

Connect to Azure SQL Database

Azure SQL Database requires encrypted connections by default. Specify encrypt=true and TrustServerCertificate=false explicitly so the connection uses TLS and validates the server certificate:

db, err := sql.Open("sqlserver",
    "sqlserver://<user>:<password>@<server>.database.windows.net?database=<database>&encrypt=true&TrustServerCertificate=false")
if err != nil {
    panic(err)
}

Note

When you omit encrypt, the driver doesn't automatically add Azure-specific TLS settings. Keep encrypt=true&TrustServerCertificate=false in Azure SQL connection strings.

Microsoft Entra ID authentication eliminates passwords from your connection strings. ActiveDirectoryDefault automatically selects the best available credential for the environment, which makes it convenient for development:

import (
    "database/sql"
    "log"

    _ "github.com/microsoft/go-mssqldb/azuread"
)

func main() {
    db, err := sql.Open("azuresql",
        "sqlserver://<server>.database.windows.net?database=<database>&fedauth=ActiveDirectoryDefault&encrypt=true&TrustServerCertificate=false")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
}

Important

ActiveDirectoryDefault is convenient for development, but it can add connection latency because it probes multiple credential sources. For production services, prefer an explicit method such as ActiveDirectoryManagedIdentity or ActiveDirectoryServicePrincipal.

How ActiveDirectoryDefault resolves credentials

ActiveDirectoryDefault tries the following credential sources in order and uses the first one that succeeds:

Order Credential source Typical environment
1 Environment variables (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET) CI/CD pipelines, Docker containers
2 Workload identity Kubernetes pods with Azure Workload Identity
3 Managed identity Azure VMs, App Service, Container Apps, Azure Functions
4 Azure CLI (az login) Local development
5 Azure Developer CLI (azd auth login) Local development

This credential chain makes ActiveDirectoryDefault convenient during development, but the sequential probing adds latency to every new connection. For production, specify the exact authentication method (such as ActiveDirectoryManagedIdentity) so the driver skips unnecessary checks.

Applications hosted in Azure (App Service, Container Apps, Azure Functions, or Azure VMs) should use a managed identity with an explicit fedauth value. This approach avoids the credential-chain overhead and removes any dependency on environment variables or CLI state.

System-assigned managed identity:

sqlserver://<server>.database.windows.net?database=<database>&fedauth=ActiveDirectoryManagedIdentity&encrypt=true&TrustServerCertificate=false

User-assigned managed identity (specify the client ID):

sqlserver://<server>.database.windows.net?database=<database>&fedauth=ActiveDirectoryManagedIdentity&user id=<client-id>&encrypt=true&TrustServerCertificate=false

Grant the identity access in the database

After configuring the managed identity on the Azure resource, create a contained database user:

CREATE USER [my-app-identity] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [my-app-identity];
ALTER ROLE db_datawriter ADD MEMBER [my-app-identity];

For system-assigned identities, use the Azure resource name. For user-assigned identities, use the identity name.

Service principal for automation

For CI/CD pipelines or service-to-service authentication:

sqlserver://<server>.database.windows.net?database=<database>&fedauth=ActiveDirectoryServicePrincipal&user id=<client-id>&password=<client-secret>&encrypt=true&TrustServerCertificate=false

For all credential types, see Microsoft Entra ID authentication.

Configure the Azure firewall

Azure SQL Database uses a server-level firewall. You must allow your client's public IP address, or use a private endpoint.

Error: Cannot open server

This error message indicates that the Azure firewall is blocking your client IP address:

mssql: login error: Cannot open server '<server>' requested by the login.
Client with IP address '<client-ip>' is not allowed to access the server.

Solutions:

  1. Add a firewall rule in the Azure portal: SQL server > Networking > Add a firewall rule.
  2. Enable Allow Azure services and resources to access this server if your application runs in Azure.
  3. For private connectivity, configure a private endpoint.

Error: Connection timed out

If the connection times out without a clear error, the firewall is likely blocking the connection silently. Verify the firewall rules first.

Connection limits by service tier

Azure SQL Database enforces connection limits per database based on the service tier. Exceeding the limit causes authentication failures for new connections. For the full limits tables, see DTU single database resource limits and vCore single database resource limits.

Set MaxOpenConns to match your tier

Always set MaxOpenConns to a value below the connection limit for your Azure SQL tier:

// Example for S2 tier (60 max workers).
// Leave headroom for Azure management connections and other clients.
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)

Tip

If multiple applications share the same database, divide the connection limit across all applications. For example, if three services share an S2 database (60 max workers), allocate 15-20 connections per service.

Handle Azure SQL throttling

Azure SQL Database can throttle connections and queries when the database approaches resource limits (CPU, IO, memory, or session count). Throttling manifests as specific error numbers.

Common throttling errors

Error number Message pattern Cause
10928 Resource ID: %d. The %s limit for the database is %d and has been reached. Session or worker limit reached.
10929 Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d. Resource governor throttling.
40501 The service is currently busy. General throttling. Retry.
40544 The database has reached its size quota. Database size limit reached. Increase capacity or free space before retrying.
40549 Session is terminated because you have a long-running transaction. Transaction exceeded time limit.
40550 Session is terminated because of too many locks. Excessive lock acquisition.
40551 Session is terminated because of excessive tempdb usage. Excessive tempdb usage.
40552 Session is terminated because of excessive transaction log usage. Transaction log space exceeded.
40553 Session is terminated because of excessive memory usage. Excessive memory consumption.
40613 Database '%.*ls' on server '%.*ls' is not currently available. Database being moved or reconfigured.
49918 Cannot process request. Not enough resources to process request. Resource exhaustion.
49919 Cannot process create or update request. Too many concurrent create/update operations.
49920 Cannot process request. Too many operations in progress. Concurrent operation limit reached.

Retry throttled requests

Most Azure SQL throttling and availability errors in the preceding table are transient and should be retried with exponential backoff. Error 40544 isn't transient. It means the database reached its size quota, so the operation won't succeed until you scale up the database or delete data.

For a complete retry implementation, see Error handling and retry patterns.

import (
    "errors"

    mssql "github.com/microsoft/go-mssqldb"
)

func isAzureThrottling(err error) bool {
    var mssqlErr mssql.Error
    if !errors.As(err, &mssqlErr) {
        return false
    }
    switch mssqlErr.Number {
    case 10928, 10929, 40501, 40549, 40550, 40551, 40552, 40553,
        40613, 49918, 49919, 49920:
        return true
    }
    return false
}

Connection resilience

Azure SQL Database occasionally reconfigures servers for updates, failovers, and load balancing. These events drop existing connections, which surface as driver: bad connection errors. Configure your pool to recover automatically:

db.SetConnMaxLifetime(5 * time.Minute)  // Rotate connections so stale ones are replaced.
db.SetConnMaxIdleTime(2 * time.Minute)  // Recycle before Azure gateway drops idle connections (30 min).
db.SetMaxIdleConns(10)                  // Keep warm connections for quick recovery.

Note

The Azure SQL gateway closes connections that are idle for about 30 minutes. Set ConnMaxIdleTime well below this threshold to avoid driver: bad connection errors on the first query after an idle period. For non-transactional calls, database/sql retries automatically on a new connection. For transactional calls, your code must catch the error and retry the entire transaction.

Reconnection after failover

Outside transactions, database/sql can transparently retry a call that starts on a bad connection when the driver marks the connection as unusable. This behavior isn't a full transient-fault retry policy for throttling, failover, or other retryable SQL errors. Wrap your database calls in a retry function to handle those scenarios:

var count int
err := RetryFunc(ctx, DefaultRetryConfig, func(ctx context.Context) error {
    return db.QueryRowContext(ctx, "SELECT COUNT(*) FROM HumanResources.Employee").Scan(&count)
})

See Error handling and retry patterns for the RetryFunc implementation.

Azure SQL Managed Instance

Azure SQL Managed Instance supports the same driver features as on-premises SQL Server, with a few differences:

Feature Azure SQL Database Azure SQL Managed Instance
SQL Server Agent Not available Available
Cross-database queries Not available Available
Linked servers Not available Available
Named pipes Not available Not available (TCP only)
Shared memory Not available Not available (TCP only)
Windows authentication (SSPI) Not available Available within the managed VNet

Connect to a Managed Instance:

sqlserver://<user>:<password>@<instance>.database.windows.net?database=<database>&encrypt=true&TrustServerCertificate=false

SQL database in Microsoft Fabric

Important

SQL database in Fabric requires Microsoft Entra ID authentication. SQL Server authentication isn't supported.

For production workloads, prefer an explicit fedauth mode instead of ActiveDirectoryDefault to avoid credential-chain probing overhead on new connections.

SQL database in Fabric supports the go-mssqldb driver with Microsoft Entra ID authentication:

db, err := sql.Open("azuresql",
    "sqlserver://<server>.database.fabric.microsoft.com?database=<database>&fedauth=ActiveDirectoryDefault&encrypt=true&TrustServerCertificate=false")
if err != nil {
    panic(err)
}

Azure SQL performance tips

Tip Details
Use connection pooling Azure SQL counts each open connection toward the tier limit. Keep MaxOpenConns bounded.
Enable encrypt=strict For the strongest security, use TDS 8.0 encryption: encrypt=strict. Azure SQL Database supports strict mode.
Use ApplicationIntent=ReadOnly Route read-heavy queries to read replicas: ApplicationIntent=ReadOnly. Available on Premium, Business Critical, and Hyperscale tiers.
Monitor DTU/vCore usage High CPU, IO, or worker usage indicates your tier might be undersized. Use Azure Monitor to track resource utilization.
Keep transactions short Azure SQL terminates sessions with transactions that exceed resource thresholds (error 40549).
Use regional endpoints Place your application in the same Azure region as the database to minimize latency.

Azure SQL troubleshooting checklist

Symptom Likely cause Solution
Cannot open server Firewall rule missing Add your IP or enable Azure services access.
Login failed Wrong credentials or missing database user Verify the login exists and has database access.
Connections time out intermittently Server reconfiguration or failover Implement retry logic and connection rotation.
Resource limit reached Too many concurrent connections Lower MaxOpenConns and close connections promptly.
The service is currently busy Azure SQL throttling Retry with exponential backoff. Consider scaling up.
Slow queries after working fine DTU/vCore exhaustion Check Azure Monitor metrics. Scale up or optimize queries.