sslmode rozhoduje o dvou oddělených věcech: zda je spojení s PostgreSQL vůbec šifrované a zda klient ověřuje, že na druhé straně je opravdu server, ke kterému se chtěl připojit. Na obojí odpovídají ano jen dvě ze šesti hodnot - verify-ca a verify-full - a pouze verify-full kontroluje i název hostitele. Výchozí hodnota knihovny libpq, prefer, na první otázku odpovídá "možná" a na druhou "ne", a dělá to bez jakéhokoli upozornění. Pokud spojení vede přes jakoukoli síť, kterou nevlastníš, nastav sslmode=verify-full, dej klientovi správný kořenový certifikát v sslrootcert a na serveru zakaž vše, co není TLS, pomocí řádků hostssl v pg_hba.conf.
Zbytek článku popisuje, co jednotlivé režimy dělají na drátě, kam patří soubory s certifikáty, jak na serveru zapnout TLS, co se stejným nastavením dělají různé ovladače (neshodují se) a jak číst chybové hlášky. Základy připojování zvenčí - connection stringy, pořadí v pg_hba.conf, příkazy psql - najdeš v článku vzdálená připojení k PostgreSQL; tady jdeme hlouběji jen do části o TLS.
Šest režimů a co který kontroluje#
PostgreSQL naváže TLS spojení neobvyklým způsobem. Klient otevře obyčejné TCP spojení na port 5432 a pošle malý paket SSLRequest s dotazem, zda server bude mluvit TLS. Server odpoví jedním bajtem: S znamená ano, N ne. Všechno, co sslmode dělá, se točí kolem toho bajtu - co klient udělá, když dostane N, a jak moc kontroluje po zahájení handshaku.
| Režim | Šifrování | Co se stane, když server řekne ne |
|---|---|---|
disable | Nikdy | Nešifrované spojení, žádný dotaz se neposílá |
allow | Jen když to vynutí server | Nejdřív nešifrovaně, TLS až po odmítnutí |
prefer | Pokud ho server nabídne | Tiše přejde na nešifrované spojení |
require | Vždy | Spojení selže |
verify-ca | Vždy | Spojení selže |
verify-full | Vždy | Spojení selže |
A druhá otázka, ta, na které proti útočníkovi záleží:
| Režim | Kontrola řetězu certifikátů | Kontrola názvu hostitele |
|---|---|---|
disable, allow, prefer | Ne | Ne |
require | Ne (viz poznámka níže) | Ne |
verify-ca | Ano, proti sslrootcert | Ne |
verify-full | Ano, proti sslrootcert | Ano |
Podrobněji:
- `disable` nikdy neposílá žádost o TLS. Dává smysl jen na Unix socketu nebo na loopbacku na stejném stroji, kde není síť, kterou by bylo potřeba chránit.
- `allow` zkusí nejdřív nešifrované spojení a s TLS to zkusí znovu, jen když server to nešifrované odmítne - typicky proto, že
pg_hba.confmá pro tebe jen řádkyhostssl. Existuje kvůli kompatibilitě a skoro nikdo by ho neměl volit. - `prefer` o TLS požádá, použije ho, když server řekne ano, a tiše pokračuje v prostém textu, když server řekne ne nebo handshake selže. O certifikátu nekontroluje nic.
- `require` trvá na šifrování a selže, pokud ho nezíská, ale přijme jakýkoli certifikát, který server předloží, včetně self-signed certifikátu vyrobeného někým uprostřed.
- `verify-ca` trvá na šifrování a kontroluje, že certifikát serveru vede k CA v kořenovém souboru, kterému důvěřuješ.
- `verify-full` dělá všechno výše a navíc kontroluje, že jméno, na které ses připojil (parametr
host), odpovídá Subject Alternative Name v certifikátu, případně Common Name, pokud SAN chybí.
Proč je výchozí prefer slabý#
prefer šifruje, když to jde, takže působí bezpečně. Problém je v tom, že rozhodnutí o šifrování padá v čistém textu, ještě předtím, než do hry vstoupí jakýkoli certifikát. Kdokoli, kdo může zasahovat do provozu mezi tebou a databází - nepřátelská Wi-Fi, napadený router, špatně nastavená proxy - může na SSLRequest odpovědět za server písmenem N. Klient pokrčí rameny a pošle autentizaci i všechny dotazy v prostém textu. Na straně klienta se nic nezaloguje a nic nevyhodí chybu.
I když prefer TLS session dostane, nikdy se nepodívá, čí certifikát vlastně obdržel, takže útočník může stejně snadno ukončit TLS sám a otevřít druhé spojení na skutečný server. Šifrování vůči straně, kterou jsi neidentifikoval, chrání jen před lidmi, kteří provoz pasivně zachytávají, a před nikým jiným.
Netýká se to jen teoretických případů ani hesla. U autentizace scram-sha-256 heslo samotné po drátě nikdy nepřijde, což pomáhá, ale následná session - každý řádek, který čteš a zapisuješ - po drátě jde. A se starší metodou md5 nebo s password degradované spojení vydá přihlašovací údaje, které jde přehrát nebo prolomit.
Proč je tedy výchozí pořád prefer? Protože díky němu spojení fungují hned po instalaci jak se servery s TLS, tak bez něj, a změna by rozbila obrovské množství existujících nasazení. Je to kompromis kvůli kompatibilitě, ne bezpečnostní doporučení, a dokumentace PostgreSQL to říká výslovně. Řešení je jednoduché: nespoléhej na něj. Uveď režim v každém connection stringu nebo v PGSSLMODE, aby bylo chování napsané a ne zděděné.
Hrubé pravidlo:
| Situace | Režim, který použít |
|---|---|
| Unix socket nebo loopback na stejném stroji | disable nebo nech výchozí |
| Soukromá síť, kterou celou ovládáš | minimálně require |
| Cokoli přes internet | verify-full |
| Certifikát serveru od veřejné CA, klient PG 16+ | verify-full s sslrootcert=system |
Nastavení sslmode: URI, klíčová slova a PGSSLMODE#
Existují tři místa, odkud klient založený na libpq režim převezme, a explicitní hodnota vždy přebije náhradní.
# URI form - parameters go after the ?$ psql "postgresql://appuser@db.example.com:5432/shop?sslmode=verify-full&sslrootcert=/etc/ssl/pg/root.crt"# Keyword form - easier to read in scripts$ psql "host=db.example.com dbname=shop user=appuser sslmode=verify-full sslrootcert=/etc/ssl/pg/root.crt"# Environment variables - used when the string leaves them out$ export PGSSLMODE=verify-full$ export PGSSLROOTCERT=/etc/ssl/pg/root.crt$ psql -h db.example.com -U appuser -d shopK TLS patří proměnné prostředí PGSSLMODE, PGSSLROOTCERT, PGSSLCERT, PGSSLKEY a PGSSLCRL. Čtou je psql, pg_dump, pg_restore, psycopg i všechno ostatní postavené na libpq. Jsou pohodlný způsob, jak zpřísnit celý stroj nebo kontejner bez úprav každého skriptu, a zároveň matoucí, když zapomenuté PGSSLMODE=disable v profilu shellu způsobí, že se spojení chová jinak než stejný řetězec jinde. Když se chování mezi dvěma stroji liší, první věc ke spuštění je env | grep ^PG. Existuje i starší proměnná PGREQUIRESSL, zastaralá ve prospěch PGSSLMODE; když jsou nastavené obě, vyhrává PGSSLMODE.
V URI se hodnoty parametrů dekódují z procentového zápisu jako všechno ostatní. Cesta na Windows jako C:\certs\root.crt projde beze změny, ale cesta obsahující &, # nebo ? potřebuje escapování a mezery mají být %20. Zápis s klíčovými slovy se tomu všemu vyhne.
Abys potvrdil, co jsi opravdu dostal, podívej se na banner při připojení psql. TLS session vypíše řádek podobný tomuto:
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)Novější klienti přidávají další pole, například protokol ALPN u PostgreSQL 17. Žádný takový řádek znamená žádné TLS. \conninfo uvnitř psql říká totéž a z jakéhokoli klienta se můžeš zeptat přímo serveru:
SELECT ssl, version, cipherFROM pg_stat_sslWHERE pid = pg_backend_pid();Vynech klauzuli WHERE a připoj pg_stat_activity (jako role, která smí vidět cizí session), a zkontroluješ všechna spojení najednou - rychlý způsob, jak najít tu jednu službu, která se pořád připojuje nešifrovaně.
Soubory s certifikáty: sslrootcert, sslcert, sslkey#
Na straně klienta se používají tři soubory a libpq má pro každý výchozí umístění:
| Parametr | Výchozí na Linuxu a macOS | Účel |
|---|---|---|
sslrootcert | ~/.postgresql/root.crt | CA, která podepsala certifikát serveru |
sslcert | ~/.postgresql/postgresql.crt | Klientský certifikát, pokud ho server vyžaduje |
sslkey | ~/.postgresql/postgresql.key | Soukromý klíč ke klientskému certifikátu |
Na Windows leží stejné soubory pod %APPDATA%\postgresql\. Seznam odvolaných certifikátů jde zadat pomocí sslcrl (výchozí root.crl), pokud provozuješ vlastní CA a certifikáty odvoláváš.
sslrootcert je ten, který potřebuje každý, kdo chce verify-ca a verify-full. Co do něj patří, závisí na tom, kdo certifikát serveru podepsal:
- Tvoje vlastní CA nebo soukromá CA poskytovatele. Soubor s certifikátem CA ve formátu PEM. Pokud server používá jediný self-signed certifikát bez CA, je ten certifikát sám svým kořenem a patří do tohoto souboru.
- Veřejná CA, například Let's Encrypt. U klientů PostgreSQL 16 a novějších použije
sslrootcert=systemúložiště důvěryhodných certifikátů operačního systému. Jeho nastavení také změní výchozí režim naverify-fulla slabší režimy z toho udělá chybu, což je záměr: důvěřovat všem veřejným CA bez kontroly názvu hostitele by dovolilo komukoli s jakýmkoli veřejným certifikátem vydávat se za tvoji databázi. U starších klientů nasměrujsslrootcertna systémový soubor s balíčkem certifikátů, například/etc/ssl/certs/ca-certificates.crtna Debianu a Ubuntu.
sslcert a sslkey mají smysl jen tehdy, když je server nastavený tak, aby klientský certifikát vyžadoval - buď jako způsob přihlášení (cert), nebo jako další kontrolu navíc k heslu. Soubor s klíčem nesmí být čitelný pro nikoho jiného: libpq na Unixu odmítne klíč s právy pro skupinu nebo ostatní, takže chmod 0600 ~/.postgresql/postgresql.key je součástí nastavení. PostgreSQL 16 přidalo sslcertmode (disable, allow, require), které řídí, zda klient certifikát vůbec posílá.
PostgreSQL 17 přidalo ještě jednu možnost, o které stojí za to vědět: sslnegotiation=direct přeskočí výměnu SSLRequest a zahájí TLS handshake okamžitě, čímž ušetří jeden round trip. Vyžaduje server PostgreSQL 17 a sslmode nastavený na require nebo přísnější. Výchozí hodnota postgres je klasické vyjednávání a funguje všude.
Strana serveru: ssl = on, certifikáty a hostssl#
Na klientovi nezáleží, pokud server TLS nikdy nenabídne. Na serveru, který spravuješ, ho zapnou tři nastavení v postgresql.conf:
ssl = onssl_cert_file = 'server.crt'ssl_key_file = 'server.key'ssl_min_protocol_version = 'TLSv1.2'Relativní cesty se vyhodnocují vůči datovému adresáři. Balíčky pro Debian a Ubuntu se dodávají s ssl = on ukazujícím na snakeoil certifikát vygenerovaný při instalaci, což stačí pro require, ale pro ověřování je k ničemu, protože mu nikdo nevěří a jeho jméno je název stroje. ssl_min_protocol_version má od PostgreSQL 13 výchozí hodnotu TLSv1.2, takže řádek výše jen opakuje výchozí stav.
Self-signed certifikát pro testování podle vzoru z dokumentace PostgreSQL:
$ openssl req -new -x509 -days 365 -nodes -text \ -out server.crt -keyout server.key -subj "/CN=db.example.com"$ chmod 0600 server.key$ chown postgres:postgres server.key server.crtServer je ke svému klíči stejně přísný jako klient: odmítne spustit TLS, pokud je server.key čitelný pro skupinu nebo ostatní, ledaže soubor patří rootovi s právy 0640. Od PostgreSQL 10 se nastavení SSL projeví po reloadu (SELECT pg_reload_conf();), ale chyba se chová různě podle toho, jak změnu aplikuješ: reload zapíše SSL configuration was not reloaded, zatímco restart s ssl = on a nečitelným certifikátem skončí tím, že server vůbec nenastartuje. Po každé změně zkontroluj log serveru.
Pro cokoli, co budou klienti ověřovat, použij certifikát, jehož Subject Alternative Name obsahuje přesný název hostitele, na který se klienti připojují. Certifikát pro db.example.com neprojde verify-full, když se aplikace připojí na IP adresu, a naopak.
Zapnutí TLS nikoho nenutí ho používat. To je úkol pg_hba.conf:
# TYPE DATABASE USER ADDRESS METHODhostssl shop appuser 0.0.0.0/0 scram-sha-256hostssl shop backup 203.0.113.20/32 certhostnossl all all 0.0.0.0/0 rejectŘádky hostssl odpovídají jen TLS spojením, řádky hostnossl jen nešifrovaným a řádky host obojímu. Když jsou pro vzdálené adresy jen řádky hostssl, degradovaný klient s prefer se prostě nemůže přihlásit - server se tak stává záchrannou sítí pro klienty, které nekontroluješ. Metoda cert autentizuje samotným klientským certifikátem a porovnává jeho Common Name se jménem role (nebo přes map). Pokud chceš kromě hesla vyžadovat i platný klientský certifikát, přidej na řádek hostssl volbu clientcert=verify-ca nebo clientcert=verify-full; server pak potřebuje ssl_ca_file ukazující na CA, která klientské certifikáty podepisuje. Starší tvar clientcert=1 byl v PostgreSQL 14 odstraněn.
U spravované databáze obvykle nic z toho upravit nemůžeš, takže otázka zní, co nabízí poskytovatel. Vyzkoušej to: psql "...?sslmode=require" se buď připojí, nebo selže s "server does not support SSL", a banner ti řekne protokol. Pak se poskytovatele zeptej, která CA certifikát podepsala a na jaký název hostitele se vztahuje, protože to je to, co potřebuje verify-full. Na RE:NODE se k serveru PostgreSQL připojuješ na hostiteli a portu uvedených u tarifu, s heslem superuživatele vygenerovaným pro daný server - TLS banner si zkontroluj na vlastním spojení a nepředpokládej to ani v jednom směru a soubory s certifikáty, které aplikace potřebuje, měj u jejího kódu, nahrané přes správce souborů nebo přes SFTP. Zabezpečení kolem TLS je shrnuté v bezpečnostním checklistu databází.
Ovladače: stejná slova, jiné chování#
Všichni klienti založení na libpq se chovají tak, jak je popsáno výše. Ovladače, které protokol implementují samy, přijímají stejná slova sslmode, ale ne vždy pod nimi myslí totéž.
psql a psycopg
psql, pg_dump, psycopg 2 i psycopg 3 používají libpq, takže režimy, výchozí hodnoty, soubory i proměnné prostředí jsou totožné. Jakýkoli parametr libpq lze předat jako pojmenovaný argument:
import osimport psycopgconn = psycopg.connect( host="db.example.com", dbname="shop", user="appuser", password=os.environ["DB_PASSWORD"], sslmode="verify-full", sslrootcert="/app/certs/root.crt",)node-postgres
node-postgres (pg) libpq nepoužívá. Bez jakéhokoli nastavení SSL se připojí v prostém textu. V connection stringu jeho parser (pg-connection-string) bere prefer, require, verify-ca i verify-full všechny jako "TLS zapnuto, s plným ověřením proti výchozímu úložišti důvěry v Node" - přísněji než libpq u prvních tří, a proto řetězec, který v psql s require funguje, v Node selže s self-signed certificate in certificate chain. Přidání uselibpqcompat=true do řetězce přepne na významy z libpq. sslmode=no-verify vypne ověřování úplně, což je v libpq totéž co require a o nic lepší.
Správné řešení není rejectUnauthorized: false, nejvíc kopírovaný řádek v celé téhle oblasti, který vypíná kontrolu, díky které má TLS cenu. Dej Node místo toho CA:
import fs from "node:fs";import { Pool } from "pg";const pool = new Pool({ host: "db.example.com", database: "shop", user: "appuser", password: process.env.DB_PASSWORD, ssl: { ca: fs.readFileSync("/app/certs/root.crt", "utf8") },});Jedna past: pokud předáš connectionString obsahující sslmode i objekt ssl, nastavení SSL z řetězce nahradí tvoje a ca, které jsi dodal, se tiše ignoruje. Použij jedno nebo druhé.
JDBC
Ovladač PostgreSQL JDBC (pgjdbc) má také vlastní implementaci. V současných verzích je jeho sslmode ve výchozím stavu prefer, stejně jako u libpq; nastavení ssl=true bez režimu znamená verify-full. Jeho výchozí soubory zrcadlí ty z libpq, ale klíč se liší: hledá postgresql.pk8 a čte klíče PKCS-12 (.p12, .pfx) nebo PKCS-8 (.pem, .der), ne tradiční PEM klíč, který používá libpq. Starší verze ovladače se chovaly jinak, takže než výchozí hodnotě uvěříš, zkontroluj verzi v souboru buildu.
spring.datasource.url=jdbc:postgresql://db.example.com:5432/shop?sslmode=verify-full&sslrootcert=/app/certs/root.crtsslfactory=org.postgresql.ssl.NonValidatingFactory vypíná ověřování a je JDBC ekvivalentem rejectUnauthorized: false. Každý jeho výskyt ber jako chybu, kterou je třeba odstranit.
Další ovladače - pgx pro Go, Npgsql pro .NET, tokio-postgres pro Rust - mají každý své výchozí hodnoty a své pravopisy. Než nasadíš, přečti si v dokumentaci ovladače slovo sslmode a soubor s CA drž v nasazení stejně jako ostatní konfiguraci; kam takové věci patří, popisuje článek proměnné prostředí a tajné údaje.
Chyby SSL a co znamenají#
`server does not support SSL, but SSL was required` - klient požádal o require nebo přísnější režim a server odpověděl N. Buď je na serveru ssl = off, certifikát se při startu nepodařilo načíst (hledej to v logu serveru), nebo odpovídá něco mezi vámi, třeba TCP proxy, místo PostgreSQL. node-postgres hlásí stejnou situaci jako The server does not support SSL connections. Neopravuj to snížením režimu; oprav server nebo se zeptej poskytovatele.
`root certificate file "/home/app/.postgresql/root.crt" does not exist` - požádal jsi o verify-ca nebo verify-full a nezadal sslrootcert, takže libpq hledal ve výchozím umístění a nic nenašel. Novější klienti přidávají nápovědu s návrhem sslrootcert=system nebo slabšího režimu. Nasměruj sslrootcert na správný soubor; v kontejnerech pamatuj, že domovský adresář je často /root nebo vůbec neexistuje.
`SSL error: certificate verify failed` - certifikát serveru nevede k CA v sslrootcert. Obvykle je to špatný soubor (certifikát serveru jiného stroje nebo balíček bez intermediate certifikátu), nebo veřejný certifikát ověřovaný proti soukromému kořeni. openssl s_client -starttls postgres -connect db.example.com:5432 (OpenSSL 1.1.1 a novější) ukáže řetěz, který server skutečně posílá.
`server certificate for "db.example.com" does not match host name "10.0.0.5"` - verify-full dělá svou práci. Připoj se pod jménem, které certifikát pokrývá, nebo vystav certifikát znovu se jménem či adresou, kterou používáš, v Subject Alternative Name.
`SSL SYSCALL error: EOF detected` nebo `SSL SYSCALL error: Connection reset by peer` - TCP spojení pod TLS session se zavřelo bez řádného ukončení TLS. Problém s certifikátem to bývá jen zřídka. Běžné příčiny: proces serveru byl zabit (hledej v logu serveru zastavení kvůli nedostatku paměti), server se restartoval, nebo NAT brána, firewall či load balancer zahodily nečinné spojení. Pokud se to děje nečinným spojením v poolu, nastav idle timeout poolu pod hodnotu sítě a zapni TCP keepalive - nastavení popisuje článek connection pooly a limity.
`private key file "/home/app/.postgresql/postgresql.key" has group or world access` - udělej na souboru chmod 0600. V Dockeru soubory připojené z hostitele s Windows nebo macOS často přijdou s volnými právy; zkopíruj klíč do image nebo do volume a nastav práva tam.
`no pg_hba.conf entry for host "...", user "appuser", database "shop", no encryption` - server pro tebe přijímá jen TLS a klient se připojil v prostém textu. Něco nastavilo disable nebo allow, nebo ovladač ve výchozím stavu TLS nepoužívá. Nastav režim explicitně.
K selháním certifikátů obecně - prošlé certifikáty, chybějící intermediate, špatná jména - platí stejné úvahy jako u webových serverů; prochází je článek chyby SSL certifikátů vysvětleny.
FAQ#
Jaký je výchozí sslmode v PostgreSQL?
Pro libpq a vše, co je na něm postavené (psql, psycopg, pg_dump), je to prefer: použij TLS, pokud ho server nabídne, jinak přejdi na prostý text, certifikát nikdy nekontroluj. Ovladače s vlastní implementací volí vlastní výchozí hodnotu a node-postgres nepoužívá TLS vůbec, dokud mu to nenařídíš.
Je sslmode=require dost bezpečný?
Zastaví pasivní odposlech a v soukromé síti, kterou plně ovládáš, to může stačit. Nezastaví aktivního útočníka, protože se přijme jakýkoli certifikát. Přes internet použij verify-full.
Jaký je rozdíl mezi verify-ca a verify-full?
Oba kontrolují, že certifikát podepsala CA, které důvěřuješ. verify-full navíc kontroluje, že certifikát byl vystaven pro název hostitele, na který ses připojil. U veřejné CA je samotný verify-ca téměř bezcenný, protože od té CA může získat certifikát pro vlastní doménu kdokoli.
Kde seženu root.crt?
Od toho, kdo server provozuje. U serveru se self-signed certifikátem je to samotný server.crt; u soukromé CA certifikát té CA; u veřejné CA systémové úložiště důvěry přes sslrootcert=system u klientů PostgreSQL 16 a novějších.
Platí sslmode i pro spojení přes Unix socket?
Ne. TLS se používá jen přes TCP. Spojení přes /var/run/postgresql sslmode ignoruje, a proto lokální psql funguje i s nastaveným PGSSLMODE=verify-full a bez jediného certifikátu.
Mohu TLS vynutit ze serveru, místo abych spoléhal na klienty?
Ano. Použij v pg_hba.conf pro vzdálené adresy jen řádky hostssl a pro přehlednost přidej řádek hostnossl ... reject. Nešifrovaný klient se pak nepřihlásí, místo aby jeho session unikla. Neplatí to jako náhrada za verify-full na klientovi, které jako jediné zabrání vydávání se za server.


