11 min czytania

Jak klient znajduje SQL Server: instancje, SQL Browser i porty

Błąd 26 po restarcie serwera oznacza, że klient nie ustalił portu instancji nazwanej. Skąd sterownik bierze nazwę, port i protokół, kiedy potrzebny jest SQL Browser na UDP 1434 i dlaczego nowe sterowniki wymagają zaufanego certyfikatu serwera.

Rys. 1. Adres serwer\INSTANCJA działa tylko wtedy, gdy SQL Browser odpowie na UDP 1434; adres z portem prowadzi od razu do instancji, a w obu przypadkach nowe sterowniki sprawdzają certyfikat serwera.

Po comiesięcznych aktualizacjach i restarcie serwera bazodanowego aplikacja kadrowa przestaje łączyć się z bazą. W jej dzienniku pojawia się komunikat:

A network-related or instance-specific error occurred while establishing a connection to SQL Server.
The server was not found or was not accessible. Verify that the instance name is correct and that
SQL Server is configured to allow remote connections.
(provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

Usługa instancji działa, a SSMS uruchomiony na samym serwerze łączy się bez problemu. Zatrzymana jest usługa SQL Server Browser, której typ uruchomienia ktoś wcześniej zmienił na ręczny. Aplikacja łączy się adresem srv-sql-01\HR i bez tej usługi nie ma skąd wziąć portu instancji.

Żeby nawiązać połączenie, klient musi znać adres serwera, protokół oraz port albo nazwę potoku instancji. Część z tych danych podaje ciąg połączenia, resztę sterownik ustala sam. Ten wpis pokazuje, skąd je bierze i jak zbudować połączenie, które nie zależy od usług mogących nie wstać po restarcie.

Instancja domyślna i instancje nazwane

Na jednym serwerze może działać jedna instancja domyślna i wiele instancji nazwanych. Każda to osobna usługa Windows z własnym procesem sqlservr.exe, własną konfiguracją sieci i własnym portem:

MSSQLSERVER    instancja domyślna       srv-sql-01         TCP 1433
MSSQL$CRM      instancja nazwana CRM    srv-sql-01\CRM     TCP, port dynamiczny
SQLBrowser     SQL Server Browser       -                  UDP 1434

Instancja domyślna nie ma nazwy w adresie: klient łączy się samą nazwą serwera i portem 1433, a przez potoki nazwane potokiem \\srv-sql-01\pipe\sql\query. Instancja nazwana ma usługę MSSQL$NAZWA, adres serwer\NAZWA i potok \\srv-sql-01\pipe\MSSQL$CRM\sql\query. Dwie instancje nie mogą używać tego samego portu, dlatego instancje nazwane domyślnie dostają port dynamiczny, wybierany przy starcie usługi. W rejestrze każda instancja ma identyfikator z numerem wersji, w SQL Server 2025 na przykład MSSQL17.CRM, i pod nim przechowuje konfigurację protokołów. SQL Server Express instaluje się domyślnie jako instancja nazwana SQLEXPRESS, więc obowiązują go te same zasady.

Jak działa SQL Browser

Usługa SQL Server Browser (SQLBrowser) przy starcie zajmuje port UDP 1434, odczytuje z rejestru wszystkie instancje na serwerze i zapamiętuje ich porty TCP oraz nazwy potoków. Klient, który dostał adres srv-sql-01\CRM, najpierw wysyła na UDP 1434 krótkie zapytanie z nazwą instancji. Format opisuje specyfikacja [MC-SQLR] (SQL Server Resolution Protocol, SSRP). Browser odpowiada tekstem z portem i potokiem, a sterownik dopiero wtedy otwiera połączenie TCP z właściwym portem.

Zapytanie:   04 43 52 4D 00     bajt 0x04 (CLNT_UCAST_INST), nazwa instancji, bajt 0x00
Odpowiedź:   05 <2 bajty długości> ServerName;SRV-SQL-01;InstanceName;CRM;IsClustered;No;Version;17.0.1000.7;tcp;14331;;

Z tego mechanizmu wynikają trzy zasady:

  • Bez działającego Browsera połączenie w postaci serwer\INSTANCJA bez portu kończy się błędem 26. Połączenia z podanym portem działają dalej.
  • Instancja domyślna nie potrzebuje Browsera, dopóki nasłuchuje na porcie 1433. Gdy ma inny port, klient musi go podać, bo przy samej nazwie serwera Browsera nie pyta.
  • Według dokumentacji instalator ustawia automatyczny start Browsera przy instalacji instancji nazwanej, także Express, choć zależnie od opcji instalacji usługa może zostać zatrzymana. Zmiana typu uruchomienia na ręczny albo wyłączony daje dokładnie objaw z początku wpisu, ale dopiero przy najbliższym restarcie.

Browser przyjmuje zapytania bez uwierzytelnienia, więc reguła zapory dla UDP 1434 powinna dopuszczać tylko podsieci aplikacji, tutaj 10.10.30.0/24. Instancji z ustawieniem HideInstance Browser nie ogłasza i można się z nią połączyć wyłącznie z jawnie podanym portem.

Port dynamiczny czy stały

Port dynamiczny może się zmienić po restarcie instancji. Dla klientów, którzy pytają Browser, zmiana jest niewidoczna, ale reguła zapory na konkretny port, ręcznie zarejestrowany SPN i ciągi połączenia z portem przestają pasować. Zostaje wtedy reguła zapory dla pliku sqlservr.exe, którą unieważnia przejście na nową wersję SQL Servera, bo zmienia się ścieżka do tego pliku.

W tej serii instancje mają stałe porty: CRM 14331, ERP 14332, HR 14333, a każda ma własny rekord A, np. sql-crm.corp.example.com. Ustawienie portu w sekcji IPAll, rekordy DNS i reguły zapory w GPO opisuje część o Kerberosie. Przy stałym porcie i adresie nazwa,port aplikacje nie potrzebują Browsera. Zostaje on wygodą dla administratorów, którzy wpisują w SSMS srv-sql-01\CRM.

Formaty adresu w ciągu połączenia

Adres serwera ma ogólną postać [protokół:]serwer[\instancja][,port]. Port oddziela przecinek, bo dwukropek jest zarezerwowany dla prefiksu protokołu.

Server=srv-sql-01\CRM                              port od SQL Browsera
Server=sql-crm.corp.example.com,14331              port podany wprost
Server=tcp:sql-crm.corp.example.com,14331          tylko TCP, bez prób innych protokołów
Server=np:\\srv-sql-01\pipe\MSSQL$CRM\sql\query    potok nazwany przez SMB
Server=lpc:srv-sql-01\CRM                          pamięć współdzielona, tylko lokalnie
AdresCo robi klientPorty i reguły zapory na serwerze
srv-sql-01łączy się z instancją domyślnąTCP 1433
srv-sql-01\CRMpyta SQL Browser o port, potem łączy się przez TCPUDP 1434 i port instancji; przy porcie dynamicznym reguła dla sqlservr.exe
sql-crm.corp.example.com,14331łączy się od razu z podanym portemTCP 14331
srv-sql-01\CRM,14331port ma pierwszeństwo, Browser nie jest pytanyTCP 14331
tcp:sql-crm.corp.example.com,14331używa wyłącznie TCPTCP 14331
np:\\srv-sql-01\pipe\MSSQL$CRM\sql\queryotwiera potok nazwany przez SMBTCP 445
lpc:srv-sql-01\CRMużywa pamięci współdzielonejżadne, działa tylko na tym samym komputerze

W ciągach połączenia aplikacji warto trzymać pełną nazwę DNS zamiast adresu IP. Dla adresu IP Windows nie użyje Kerberosa, a sterownik nie dopasuje go do nazwy w certyfikacie serwera.

Protokoły i SQL Server Express

SQL Server obsługuje trzy protokoły:

  • TCP/IP to podstawowy protokół połączeń sieciowych.
  • Potoki nazwane działają przez SMB (TCP 445). Po instalacji są wyłączone dla połączeń sieciowych we wszystkich edycjach.
  • Pamięć współdzielona jest włączona po każdej instalacji i działa tylko na tym samym komputerze.

Bez prefiksu sterownik próbuje kolejnych włączonych protokołów. Gdy zawiedzie TCP, komunikat może dotyczyć ostatniej próby, np. Named Pipes Provider, error: 40, chociaż przyczyna leży gdzie indziej. Prefiks tcp: w ciągu połączenia aplikacji usuwa tę niejednoznaczność.

W nowej instalacji edycji Express i Developer protokół TCP/IP jest wyłączony, w odróżnieniu od edycji Enterprise i Standard. Instancja działa, lokalne połączenia przechodzą, a z innych komputerów nie łączy się nikt. TCP/IP włącza się w SQL Server Configuration Manager (dla SQL Server 2025 plik SQLServerManager17.msc): SQL Server Network Configuration, Protocols for HR, TCP/IP, Enable. Potem trzeba zrestartować usługę instancji i usługę SQL Browser. To samo w PowerShell, dla przykładowej instancji HR w edycji Express:

# Wymaga modułu SqlServer z PowerShell Gallery
Import-Module SqlServer
$wmi = New-Object Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer 'srv-sql-01'
$tcp = $wmi.GetSmoObject("ManagedComputer[@Name='srv-sql-01']/ServerInstance[@Name='HR']/ServerProtocol[@Name='Tcp']")
$tcp.IsEnabled = $true
$tcp.Alter()

# Protokół działa po restarcie instancji, Browser odczytuje porty na nowo
Restart-Service -Name 'MSSQL$HR' -Force
Restart-Service -Name 'SQLBrowser'

Ograniczenia edycji Express i to, kiedy wystarcza, opisuje część o SQL Server Express.

Szyfrowanie domyślnie wymagane

Nowe wersje sterowników i narzędzi Microsoftu domyślnie wymagają szyfrowania połączenia i sprawdzają certyfikat serwera:

Sterownik lub narzędzieOd wersjiUstawienie domyślne
Microsoft.Data.SqlClient4.0Encrypt=True, od 5.0 Mandatory
ODBC Driver for SQL Server18Encrypt=yes (Mandatory)
OLE DB Driver for SQL Server (MSOLEDBSQL19)19Encrypt=Mandatory
SQL Server Management Studio20Encryption: Mandatory

Instancja bez skonfigurowanego certyfikatu generuje przy starcie certyfikat samopodpisany i zapisuje w dzienniku błędów A self-generated certificate was successfully loaded for encryption. Żaden klient mu nie ufa, więc po aktualizacji sterownika aplikacja, która działała latami, zgłasza błąd:

A connection was successfully established with the server, but then an error occurred during the login process.
(provider: SSL Provider, error: 0 - The certificate chain was issued by an authority that is not trusted.)

Najszybsze obejście to TrustServerCertificate=True w ciągu połączenia. Połączenie pozostaje szyfrowane, ale klient nie sprawdza tożsamości serwera, więc ktoś, kto przejmie ruch, może podstawić własny serwer. Przy Encrypt=Strict (TDS 8.0, od SQL Server 2022) to ustawienie nie działa, bo certyfikat jest sprawdzany zawsze. Encrypt=False jest gorszym wyjściem: gdy serwer nie wymusza szyfrowania, szyfrowane jest tylko logowanie, a dane płyną otwartym tekstem.

Właściwe rozwiązanie to certyfikat z wewnętrznego urzędu certyfikacji (AD CS). Komputery w domenie ufają głównemu urzędowi certyfikacji przedsiębiorstwa bez dodatkowej konfiguracji, a komputery spoza domeny i kontenery trzeba skonfigurować osobno. Certyfikat musi spełniać wymagania SQL Servera:

  • rozszerzone użycie klucza Server Authentication (1.3.6.1.5.5.7.3.1);
  • w rozszerzeniu SAN wszystkie nazwy, których używają klienci, bo sterownik porównuje z certyfikatem nazwę z ciągu połączenia: sql-crm, sql-erp i sql-hr w domenie corp.example.com oraz srv-sql-01.corp.example.com, a krótkie nazwy tylko wtedy, gdy klienci ich używają;
  • klucz w starszym dostawcy CSP, np. Microsoft RSA SChannel Cryptographic Provider (KeySpec AT_KEYEXCHANGE), bo certyfikatów z dostawcą KSP SQL Server nie przyjmie;
  • magazyn komputera (LocalMachine\My) i prawo odczytu klucza prywatnego dla konta usługi CORP\svc-sql$, nadawane w certlm.msc przez All Tasks, Manage Private Keys.

Szablon najwygodniej przygotować jako kopię szablonu Web Server, z prawem Enroll dla grupy GG-SQL-Hosts i podmiotem podawanym we wniosku. Wniosek wysłany z serwera w sesji administratora zapisuje certyfikat w magazynie komputera:

# Szablon SQLServerTLS: kopia Web Server, Enroll dla GG-SQL-Hosts, dostawca Microsoft RSA SChannel
$names = 'srv-sql-01.corp.example.com', 'sql-crm.corp.example.com', 'sql-erp.corp.example.com', 'sql-hr.corp.example.com'
Get-Certificate -Template 'SQLServerTLS' -SubjectName 'CN=srv-sql-01.corp.example.com' -DnsName $names -CertStoreLocation 'Cert:\LocalMachine\My'

Certyfikat wskazuje się dla każdej instancji osobno w Configuration Manager (Protocols for CRM, Properties, zakładka Certificate), a nowe ustawienie działa po restarcie usługi. Od SQL Server 2025 serwery połączone i zdalny dystrybutor replikacji korzystają ze sterownika OLE DB 19 z tymi samymi ustawieniami domyślnymi, więc zaufany certyfikat jest potrzebny także w ruchu między instancjami. SQL Server sprawdza ważność certyfikatu tylko przy konfiguracji, a część klientów odrzuca wygasły certyfikat przy każdym połączeniu, dlatego datę ważności warto objąć monitorowaniem certyfikatów.

Sprawdź u siebie

Usługi instancji i SQL Browsera na serwerze oraz proces nasłuchujący na UDP 1434. Dobry wynik to Running i Automatic przy każdej usłudze MSSQL$… oraz przy SQLBrowser, jeśli klienci używają adresów serwer\INSTANCJA, a także wiersz z portem 1434 należący do procesu sqlbrowser. Manual albo Disabled przy Browserze to awaria z początku wpisu, która czeka na najbliższy restart.

Get-Service -Name 'MSSQLSERVER', 'MSSQL$*', 'SQLBrowser' -ErrorAction SilentlyContinue |
    Select-Object Name, Status, StartType

# Proces nasłuchujący na UDP 1434
Get-NetUDPEndpoint -LocalPort 1434 -ErrorAction SilentlyContinue |
    Select-Object LocalAddress, LocalPort, @{ n = 'Process'; e = { (Get-Process -Id $_.OwningProcess).ProcessName } }

Porty TCP, na których nasłuchuje każda działająca instancja. Dobry wynik to port zgodny z konwencją, np. 14331 dla CRM, na adresach 0.0.0.0 i ::. Dodatkowy port na 127.0.0.1 i ::1 to lokalne połączenie administracyjne (DAC). Brak wierszy przy działającej instancji oznacza wyłączony TCP/IP, a port spoza konwencji to port dynamiczny.

Get-CimInstance -ClassName Win32_Service |
    Where-Object { ($_.Name -eq 'MSSQLSERVER' -or $_.Name -like 'MSSQL$*') -and $_.State -eq 'Running' } |
    ForEach-Object {
        $svc = $_.Name
        Get-NetTCPConnection -State Listen -OwningProcess $_.ProcessId -ErrorAction SilentlyContinue |
            Select-Object @{ n = 'Service'; e = { $svc } }, LocalAddress, LocalPort
    }

Test z komputera w podsieci aplikacji: osiągalność portu instancji przez TCP i odpowiedź SQL Browsera. TcpTestSucceeded : True oznacza otwartą drogę do portu. Skrypt wysyła jedno zapytanie CLNT_UCAST_INST i wypisuje odpowiedź, w której dobry wynik zawiera tcp;14331. Przekroczenie czasu oznacza, że Browser nie działa, zapora blokuje UDP 1434, instancja jest ukryta albo nazwa instancji jest błędna, bo na nieznane nazwy Browser nie odpowiada. Skrypt niczego nie zmienia.

Test-NetConnection -ComputerName sql-crm.corp.example.com -Port 14331 |
    Select-Object RemoteAddress, RemotePort, TcpTestSucceeded

$server = 'srv-sql-01.corp.example.com'
$instance = 'CRM'
$udp = New-Object System.Net.Sockets.UdpClient
$udp.Client.ReceiveTimeout = 3000
try {
    # CLNT_UCAST_INST: bajt 0x04, nazwa instancji, bajt 0x00
    $request = [byte[]](@(0x04) + [System.Text.Encoding]::ASCII.GetBytes($instance) + @(0x00))
    $udp.Connect($server, 1434)
    [void]$udp.Send($request, $request.Length)
    $from = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
    $response = $udp.Receive([ref]$from)
    # SVR_RESP: bajt 0x05, długość (2 bajty), tekst rozdzielony średnikami
    [System.Text.Encoding]::ASCII.GetString($response, 3, $response.Length - 3)
}
catch {
    "Brak odpowiedzi z UDP 1434: $($_.Exception.Message)"
}
finally {
    $udp.Close()
}

Szyfrowanie bieżących połączeń sieciowych i certyfikat, który instancja załadowała przy starcie. Dobry wynik to TRUE w encrypt_option przy połączeniach aplikacji i wpis o certyfikacie z odciskiem (Cert Hash) w dzienniku. Wpis A self-generated certificate was successfully loaded for encryption. oznacza certyfikat samopodpisany, a Named pipe w net_transport to klient, który nie łączy się przez TCP.

SELECT s.program_name, s.host_name, c.net_transport, c.encrypt_option, c.local_tcp_port
FROM sys.dm_exec_connections AS c
JOIN sys.dm_exec_sessions AS s ON s.session_id = c.session_id
WHERE c.net_transport <> 'Shared memory'
ORDER BY c.encrypt_option, s.program_name;

EXEC xp_readerrorlog 0, 1, N'certificate';