PostgreSQL bağlantısını TLS ile şifrelemek için sunucuda postgresql.conf içinde ssl = on yapar, veri dizinine server.crt (sertifika) ve server.key (özel anahtar) dosyalarını koyar ve yapılandırmayı yeniden yüklersiniz. Şifresiz bağlantıları kesin olarak reddetmek için pg_hba.conf içinde host yerine hostssl satırları kullanırsınız. İstemci tarafında ise sslmode parametresi belirleyicidir: require yalnızca şifreleme ister, verify-full ise sunucu sertifikasını güvendiğiniz bir kök sertifikayla doğrular ve sunucu adının sertifikadakiyle eşleştiğini kontrol eder.
Bu yazıda sertifika oluşturmayı, sunucu ayarlarını, hostssl ve hostnossl satırlarını, sslmode değerlerinin hangi saldırılara karşı koruduğunu, istemci sertifikasıyla parolasız giriş yapmayı ve pg_stat_ssl ile bağlantıyı doğrulamayı göreceksiniz. Örnekler PostgreSQL 18.1 ve OpenSSL 3.5 ile, kendi oluşturduğumuz bir kök sertifika otoritesiyle (CA) yerel bir test kümesinde çalıştırıldı. pg_hba.conf satır biçimini bilmiyorsanız önce pg_hba.conf ve uzak bağlantı yazısını okuyun; serinin tamamı için PostgreSQL öğrenme rehberi sayfasına bakabilirsiniz.
SSL ve TLS: neden gerekli?

Belgelerde SSL ve TLS terimleri birbirinin yerine kullanılır. Gerçekte kullanılan protokol TLS'dir; eski SSL protokolleri artık desteklenmez, ama ad alışkanlıkla sürmektedir. PostgreSQL bu özellik için OpenSSL kütüphanesini kullanır ve hem sunucuda hem istemcide derleme sırasında etkinleştirilmiş olması gerekir. Sunucu, normal ve şifreli bağlantıları aynı TCP portunda dinler; şifrelemenin kullanılıp kullanılmayacağı bağlantı başında istemciyle pazarlık edilir.
İstemci belgeleri TLS'nin üç tehlikeye karşı koruma sağladığını anlatır. Gizlice dinleme: ağ trafiğini görebilen biri kullanıcı adını, parolayı ve verileri okuyabilir; şifreleme bunu engeller. Ortadaki adam: araya giren biri kendini sunucu gibi gösterip trafiği şifreli olsa bile okuyup değiştirebilir; buna karşı koruma sunucu sertifikasının doğrulanmasıdır. Kimliğe bürünme: yetkisiz biri meşru bir istemci gibi bağlanabilir; buna karşı istemci sertifikaları kullanılır. Bu yazının geri kalanı bu üç katmanı sırayla kuruyor.
Sertifika oluşturma
Belgeler test için kendinden imzalı tek bir sertifikanın yeterli olabileceğini, üretimde ise bir sertifika otoritesinin imzaladığı sertifikanın kullanılması gerektiğini belirtir. Biz belgelerdeki üç adımlı yolu izledik: önce bir kök CA, sonra bu CA'nın imzaladığı bir sunucu sertifikası, en son da istemci sertifikası. Belgelerdeki komutlara iki ekleme yaptık: kök sertifikaya CA olduğunu belirten uzantıyı bir dosyadan verdik ve sunucu sertifikasına subjectAltName (SAN) ekledik:
# 1. Kök sertifika otoritesi
openssl req -new -nodes -text -out root.csr -keyout root.key -subj "/CN=Ornek Kok CA"
printf "basicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign\n" > ca.ext
openssl x509 -req -in root.csr -text -days 3650 -extfile ca.ext -signkey root.key -out root.crt
# 2. Sunucu sertifikası (ad: localhost)
openssl req -new -nodes -text -out server.csr -keyout server.key -subj "/CN=localhost"
printf "subjectAltName=DNS:localhost\n" > san.ext
openssl x509 -req -in server.csr -text -days 365 -CA root.crt -CAkey root.key \
-CAcreateserial -extfile san.ext -out server.crt
# 3. İstemci sertifikası (CN = veritabanı kullanıcı adı)
openssl req -new -nodes -text -out istemci.csr -keyout istemci.key -subj "/CN=siparis_api"
openssl x509 -req -in istemci.csr -text -days 365 -CA root.crt -CAkey root.key \
-CAcreateserial -out istemci.crt
openssl x509 -in server.crt -noout -subject -ext subjectAltName
# subject=CN=localhost
# X509v3 Subject Alternative Name:
# DNS:localhostDosyaların görevleri belgelerde şöyle dağıtılır: server.crt ve server.key sunucuda, root.crt istemcide durur; root.key ise yeni sertifikalar imzalamak için çevrim dışı saklanmalıdır. Ara sertifika (intermediate CA) kullanıyorsanız sunucu sertifikasıyla ara sertifikayı tek bir dosyada birleştirip server.crt olarak koyarsınız; dosyadaki ilk sertifika sunucunun kendi sertifikası olmalıdır, kök sertifikanın ise bu dosyaya eklenmesine gerek yoktur.
Unix sistemlerde sunucu, özel anahtar dosyasının izinleri gevşekse onu reddeder; belgeler chmod 0600 server.key (ya da root sahipliğinde 0640) ister. Parola korumalı özel anahtarlar sunucu başlarken parola sorulmasını gerektirir ve belgelere göre Windows'ta hiç kullanılamaz. Bizim örneklerimiz Windows'ta çalıştığı için chmod adımı uygulanmadı.
Sunucu tarafı: ssl = on
Sertifika ve anahtarı veri dizinine kopyaladıktan sonra postgresql.conf dosyasına iki satır ekledik. ssl_ca_file, istemci sertifikalarını doğrulamak için güvenilen CA dosyasıdır; yalnızca istemci sertifikası kullanacaksanız gerekir:
ssl = on
ssl_ca_file = 'root.crt'
# varsayılanlar: ssl_cert_file = 'server.crt', ssl_key_file = 'server.key'
# ssl_min_protocol_version = 'TLSv1.2'ssl ve dosya parametreleri sighup bağlamındadır, yani yeniden başlatma gerekmez; SELECT pg_reload_conf() yeterlidir. Belgelere göre sunucu bu dosyaları başlangıçta ve her yeniden yüklemede okur, Windows'ta ayrıca her yeni bağlantı süreci için tekrar okur. Başlangıçta dosyalarda hata varsa sunucu açılmaz; yeniden yükleme sırasında hata bulunursa dosyalar yok sayılır ve eski SSL yapılandırması kullanılmaya devam eder. Yeniden yükledikten sonra yeni bir bağlantıyla kontrol ettik:
SELECT name, setting FROM pg_settings
WHERE name IN ('ssl', 'ssl_ca_file', 'ssl_min_protocol_version') ORDER BY name;
SELECT ssl, version, cipher, bits FROM pg_stat_ssl WHERE pid = pg_backend_pid(); name | setting
--------------------------+----------
ssl | on
ssl_ca_file | root.crt
ssl_min_protocol_version | TLSv1.2
(3 rows)
ssl | version | cipher | bits
-----+---------+------------------------+------
t | TLSv1.3 | TLS_AES_256_GCM_SHA384 | 256
(1 row)pg_stat_ssl görünümü her bağlantı için bir satır tutar: şifreleme kullanılıyor mu, hangi TLS sürümü ve şifre takımı seçildi, istemci sertifikası verildiyse onun DN değeri ve seri numarası. pid sütunu üzerinden pg_stat_activity ile birleştirerek hangi kullanıcının şifresiz bağlandığını bulabilirsiniz; izleme sorguları pg_stat_activity yazısında anlatılıyor. Minimum protokol sürümünün varsayılanı belgelere göre TLSv1.2'dir; daha eski TLS sürümlerine izin vermek için özel bir neden yoksa bu değeri düşürmeyin.
PostgreSQL 18 bu alanda birkaç değişiklik getirdi: TLS 1.3 şifre takımlarını belirlemek için ssl_tls13_ciphers parametresi eklendi, ssl_ecdh_curve parametresinin adı ssl_groups oldu (eski ad hâlâ çalışıyor) ve varsayılanına X25519 eğrisi eklendi, OpenSSL 1.1.1'den eski sürümlerin desteği kaldırıldı. Ayrıntılar PostgreSQL 18 yenilikleri yazısında.
hostssl ile şifresiz bağlantıyı reddetmek
ssl = on yalnızca şifreli bağlantıya izin verir; varsayılan olarak şifreleme istemcinin tercihine kalır. Zorunlu kılmak için pg_hba.conf satırlarını buna göre yazarsınız. Test kümemizdeki dosya şöyleydi:
# TYPE DATABASE USER ADDRESS METHOD
host all postgres 127.0.0.1/32 scram-sha-256
hostssl stok siparis_api 127.0.0.1/32 cert
hostssl stok +uygulama_grubu 127.0.0.1/32 scram-sha-256
hostnossl stok all 127.0.0.1/32 rejecthostssl yalnızca şifreli TCP bağlantılarıyla, hostnossl yalnızca şifresiz olanlarla eşleşir. Son satır, stok veritabanına şifresiz gelen her bağlantıyı açık bir mesajla reddeder. Belgelere göre sunucuda SSL açık değilse hostssl satırları yalnızca bir uyarıyla yok sayılır; bu yüzden önce ssl = on ayarının etkin olduğunu doğrulayın. Şifreyi kapatarak bağlanmayı denediğimizde sonuç beklendiği gibiydi:
psql "host=127.0.0.1 port=46461 user=rapor dbname=stok sslmode=disable"
# psql: error: connection to server at "127.0.0.1", port 46461 failed: FATAL: pg_hba.conf rejects connection for host "127.0.0.1", user "rapor", database "stok", no encryptionİstemci tarafı: sslmode değerleri

libpq tabanlı bütün istemcilerde (psql, pg_dump ve libpq kullanan sürücüler) şifreleme davranışı sslmode bağlantı parametresiyle ya da PGSSLMODE ortam değişkeniyle belirlenir. Belgelerdeki tabloyu kendi sözlerimizle özetlersek:
| sslmode | Dinlemeye karşı | Ortadaki adama karşı | Anlamı |
|---|---|---|---|
| disable | Hayır | Hayır | Hiç şifreleme denenmez |
| allow | Belki | Hayır | Sunucu isterse şifreler |
| prefer (varsayılan) | Belki | Hayır | Sunucu destekliyorsa şifreler, desteklemiyorsa şifresiz devam eder |
| require | Evet | Hayır | Şifreleme zorunlu, sunucu kimliği doğrulanmaz |
| verify-ca | Evet | CA politikasına bağlı | Sertifika zinciri güvenilen köke kadar doğrulanır |
| verify-full | Evet | Evet | Zincire ek olarak sunucu adı sertifikadaki adla eşleşmelidir |
Varsayılan değer prefer'dir ve belgeler bunun güvenlik açısından anlamlı olmadığını, yalnızca geriye uyumluluk için varsayılan bırakıldığını açıkça söyler. Bizim sunucumuzda SSL açık olduğu için sslmode vermeden bağlanınca da pg_stat_ssl TLSv1.3 gösterdi; ama sunucu SSL desteklemeseydi istemci sessizce şifresiz devam edecekti. Bir bağlantının gerçekten korunduğundan emin olmanın tek yolu, istemcide verify-ca ya da verify-full kullanıp bir kök sertifika vermektir. Belgeler bunu tarayıcıda https adresi kullanmaya benzetir.
Kök sertifika varsayılan olarak Unix'te ~/.postgresql/root.crt, Windows'ta %APPDATA%\postgresql\root.crt dosyasından okunur; sslrootcert parametresi ya da PGSSLROOTCERT değişkeni bu konumu değiştirir. Dosya yokken doğrulama istemek açık bir hata verir:
psql "host=127.0.0.1 port=46461 user=rapor dbname=stok sslmode=verify-ca"
# psql: error: connection to server at "127.0.0.1", port 46461 failed: root certificate file "C:\Users\egeme\AppData\Roaming/postgresql/root.crt" does not exist
# Either provide the file, use the system's trusted roots with sslrootcert=system, or change sslmode to disable server certificate verification.verify-full ve ad eşleşmesi
verify-full modunda istemci, bağlandığı adı sertifikanın SAN alanındaki adlarla, SAN'da dNSName yoksa Common Name (CN) ile karşılaştırır. IP adresiyle bağlanıldığında IP, iPAddress ya da dNSName türündeki SAN kayıtlarıyla karşılaştırılır. Sertifikamızda yalnızca DNS:localhost olduğu için IP adresiyle bağlanmak reddedildi, aynı sunucuya localhost adıyla bağlanmak ise başarılı oldu:
psql "host=127.0.0.1 port=46461 user=rapor dbname=stok sslmode=verify-full sslrootcert=root.crt"
# psql: error: connection to server at "127.0.0.1", port 46461 failed: server certificate for "localhost" (and 1 other name) does not match host name "127.0.0.1"
psql "host=localhost hostaddr=127.0.0.1 port=46461 user=rapor dbname=stok sslmode=verify-full sslrootcert=root.crt" \
-c "select current_user, ssl, version from pg_stat_ssl where pid = pg_backend_pid()" current_user | ssl | version
--------------+-----+---------
rapor | t | TLSv1.3
(1 row)İkinci komutta host parametresi sertifika kontrolünde kullanılacak adı, hostaddr ise ad çözümlemesi yapmadan bağlanılacak adresi verir. Aynı IP ile sslmode=verify-ca denediğimizde bağlantı kuruldu, çünkü bu mod yalnızca zinciri kontrol eder, adı kontrol etmez. Belgelere göre kendi yerel CA'nızı ya da kendinden imzalı sertifikayı kullanıyorsanız verify-ca çoğu zaman yeterli korumayı sağlar; herkese açık bir CA kullanılıyorsa aynı CA'dan başkasının da sertifika almış olabileceği için her zaman verify-full kullanılmalıdır. Sertifikadaki ad * ile başlıyorsa joker karakter nokta dışındaki her şeyle eşleşir; yani alt alan adlarını kapsamaz.
İstemci sertifikasıyla giriş
Sunucunun da istemciyi doğrulaması için iki yol vardır. Birincisi pg_hba.conf içinde cert yöntemidir: istemci geçerli bir sertifika sunmak zorundadır, parola sorulmaz ve sertifikanın CN değeri istenen veritabanı kullanıcı adıyla eşleşmelidir. İkincisi, scram-sha-256 gibi başka bir yöntemin yanına clientcert=verify-ca ya da clientcert=verify-full seçeneği eklemektir; bu durumda hem parola hem sertifika istenir. Belgeler cert yöntemini, clientcert=verify-full ile birleşmiş trust olarak tanımlar. Her iki yol için de sunucuda ssl_ca_file ayarlanmış olmalıdır.
Örnek dosyamızda siparis_api için cert satırı vardı. Önce sertifika vermeden, sonra sertifikayla bağlandık:
psql "host=localhost hostaddr=127.0.0.1 port=46461 user=siparis_api dbname=stok sslmode=verify-full sslrootcert=root.crt" -w
# psql: error: connection to server at "127.0.0.1", port 46461 failed: FATAL: connection requires a valid client certificate
psql "host=localhost hostaddr=127.0.0.1 port=46461 user=siparis_api dbname=stok sslmode=verify-full sslrootcert=root.crt sslcert=istemci.crt sslkey=istemci.key" \
-c "select current_user, client_dn, issuer_dn from pg_stat_ssl where pid = pg_backend_pid()" current_user | client_dn | issuer_dn
--------------+-----------------+------------------
siparis_api | /CN=siparis_api | /CN=Ornek Kok CA
(1 row)İkinci bağlantıda hiçbir parola verilmedi; kimlik, kök CA'mızın imzaladığı ve CN değeri siparis_api olan sertifikayla kanıtlandı. İstemci sertifikası ve anahtarı varsayılan olarak ~/.postgresql/postgresql.crt ve postgresql.key dosyalarından (Windows'ta %APPDATA%\postgresql\ altından) okunur; sslcert ve sslkey parametreleri ya da PGSSLCERT ve PGSSLKEY değişkenleri bunu değiştirir. Unix'te istemci anahtarı da 0600 izinli olmalıdır; Windows'ta bu kontrol yapılmaz. CN değeri kullanıcı adından farklıysa pg_ident.conf içinde bir kullanıcı adı eşlemesi (map= seçeneği) tanımlayabilirsiniz. İptal edilen sertifikalar için sunucuda ssl_crl_file ya da ssl_crl_dir, istemcide sslcrl kullanılır.
Uygulamadan bağlanırken
libpq kullanan sürücüler aynı parametreleri bağlantı dizesinde kabul eder. Anahtar ve değer biçiminde ya da URI biçiminde yazılabilir:
# anahtar=değer biçimi
psql "host=db.ornek.com.tr dbname=stok user=rapor sslmode=verify-full sslrootcert=/etc/ssl/ornek/root.crt"
# URI biçimi
psql "postgresql://rapor@db.ornek.com.tr/stok?sslmode=verify-full&sslrootcert=/etc/ssl/ornek/root.crt"
# ortam değişkenleriyle
export PGSSLMODE=verify-full PGSSLROOTCERT=/etc/ssl/ornek/root.crtBu üç örnek uzak bir sunucu adı içerdiği için test ortamımızda çalıştırılmadı; parametre adları yukarıdaki yerel testlerle aynıdır. PHP'den PDO ile bağlanırken bağlantı ayarlarının nasıl verildiğini PHP PDO yazısında bulabilirsiniz. PostgreSQL 18 ile libpq'ya hata ayıklama için TLS anahtar bilgisini bir dosyaya yazan sslkeylogfile parametresi eklendi; bu dosya trafiğin çözülmesini sağladığı için yalnızca test ortamında kullanılmalıdır.
Sık yapılan hatalar

- ssl = on yapıp hostssl kullanmamak: sunucu şifrelemeye izin verir ama zorlamaz;
hostsatırları şifresiz bağlantıyı da kabul eder. - İstemcide varsayılan prefer ile yetinmek: sunucu SSL desteklemiyorsa ya da araya biri girerse istemci şifresiz devam edebilir.
verify-fullkullanın. - Sertifikaya SAN eklememek:
verify-fullönce SAN'a bakar; uygulamanın bağlandığı ad ya da IP sertifikada yoksa bağlantı reddedilir. - root.key dosyasını sunucuda bırakmak: belgeler kök anahtarın çevrim dışı saklanmasını ister.
- Anahtar dosyası izinlerini unutmak (Unix): izinleri
0600'den gevşek birserver.keysunucu tarafından reddedilir. - Parolalı anahtarı Windows'ta kullanmaya çalışmak: belgelere göre Windows'ta parola korumalı sunucu anahtarı kullanılamaz.
Sık Sorulan Sorular
SSL açmak için sunucuyu yeniden başlatmak gerekir mi?
Hayır. ssl ve sertifika dosyası parametreleri sighup bağlamındadır; pg_reload_conf() yeterlidir. Biz de testimizde sunucuyu yeniden başlatmadan SSL'i açtık. Yalnızca parola korumalı anahtar kullanıyorsanız belgeler yeniden yüklemenin kısıtlandığını belirtir.
Bağlantımın şifreli olduğunu nasıl anlarım?
Bağlı oturumda SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid() çalıştırın. psql 18'de \conninfo komutu da bir tablo döndürür; testimizde bu tabloda SSL Connection | true, SSL Protocol | TLSv1.3 ve kullanılan şifre takımı satırları göründü. Sunucu tarafında bütün bağlantıları görmek için pg_stat_ssl görünümünü pg_stat_activity ile birleştirin.
Kendinden imzalı sertifika yeterli mi?
Şifreleme için evet; kimlik doğrulama için sertifikayı istemciye sslrootcert olarak verip verify-ca ya da verify-full kullanmanız gerekir. Belgeler üretim ortamı için bir CA tarafından imzalanmış sertifikayı önerir; bu, kurum içi bir kök CA da olabilir.
Sertifikanın süresi dolunca ne olur?
Doğrulama yapan istemciler (verify-ca, verify-full) süresi dolmuş sertifikayı kabul etmez. Yeni sertifikayı aynı dosya adıyla koyup pg_reload_conf() çalıştırmanız yeterlidir; belgelere göre yeniden yüklemede dosyada hata bulunursa eski yapılandırma kullanılmaya devam eder ve hata günlüğe yazılır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: PostgreSQL öğrenme rehberi · pg_hba.conf ve uzak bağlantı · Kullanıcı ve rol yönetimi · postgresql.conf ve ALTER SYSTEM · Bağlantı hataları · SSH ile sunucuya bağlanma