Bölüm 5 / 13
Ağ araçları
Port, curl, ss, ssh; nginx ile ters vekil.
21 dakikalık okuma
"Uygulamaya erişemiyorum" cümlesi, bir geliştiricinin duyacağı en
belirsiz hata bildirimidir: sorun uygulamada, portta, güvenlik duvarında, DNS'te, ters
vekilde ya da sertifikada olabilir. Bu ders o belirsizliği sıralı bir teşhis
merdivenine çevirir ve merdivenin her basamağının aracını verir:
ss, ip, dig, curl, nc,
ufw/firewalld. İkinci yarıda üretim düzeni kurulur:
SSH ile güvenli erişim (16-04'ün SSH senedi burada tamamlanır),
rsync ile dosya taşıma, nginx ters vekil ve ASP.NET Core'un
ForwardedHeaders ayarı (13-02 ve 14-06 senetleri ödenir), TLS sonlandırma ve
Let's Encrypt. Dersin sonunda bir Linux sunucusunda ayakta duran, HTTPS konuşan bir
uygulama düzenini uçtan uca anlatabilir olursun.
01Ne öğreneceksin
- IP, port, soket;
ss -tulpnile dinleyen servisleri okumak;ip a/ip r - Ad çözümleme sırası:
/etc/hosts↔ DNS;dig,getentve "uygulama farklı çözüyor" tuzağı curlderinlemesine:-i -v -H -d -X --resolve, çıkış kodları;nc,ping/traceroute'un sınırları- Güvenlik duvarı (
ufw/firewalld) ve "port açık ≠ servis dinliyor" ayrımı; altı basamaklı teşhis merdiveni - SSH: anahtar üretimi,
~/.ssh/config,ssh-agent, parola girişini kapatmak,-L/-Jile tünel scp/rsyncile dosya taşıma ve yayın- nginx ters vekil:
proxy_pass,X-Forwarded-*ve ASP.NET CoreForwardedHeaders; TLS sonlandırma, certbot, HTTP→HTTPS
02IP, port, soket: kim nerede dinliyor
2. hattaki ağ dersinden hatırla: veri IP adresiyle makineye, port numarasıyla o makinedeki uygulamaya ulaşır. Bir bağlantıyı benzersiz kılan dörtlüdür (kaynak IP, kaynak port, hedef IP, hedef port) ve işletim sistemi bu bağlantıyı bir soket olarak tutar — 1. dersteki "her şey dosyadır" ilkesi gereği soket de bir dosya tanıtıcısıdır.
Sunucuda ilk sorulacak soru şudur: hangi uygulama hangi adres ve portta dinliyor?
Cevabı ss verir (eski netstat'ın yerini almıştır):
$ sudo ss -tulpn
Netid State Local Address:Port Process
tcp LISTEN 127.0.0.1:5080 users:(("dotnet",pid=4821,fd=12))
tcp LISTEN 0.0.0.0:80 users:(("nginx",pid=1180,fd=6))
tcp LISTEN [::]:22 users:(("sshd",pid=940,fd=3))
udp UNCONN 127.0.0.53:53 users:(("systemd-resolve",pid=610,fd=12))
Seçeneklerin anlamı: -t TCP, -u UDP, -l yalnız
dinleyenler, -p süreç adı (sudo gerekir), -n
numaraları çözmeden göster (hızlı). Bu çıktıdaki en önemli ayrım şudur:
| Dinlenen adres | Anlamı | Sonuç |
|---|---|---|
127.0.0.1:5080 | Yalnız bu makinenin içinden erişilebilir | Dışarıdan bağlanılamaz — kasıtlı: önüne ters vekil konur (08. bölüm) |
0.0.0.0:80 | Bütün arayüzlerde, dışarıya açık | Ters vekil, güvenlik duvarıyla korunur |
[::]:22 | IPv6 (çoğu sistemde IPv4'ü de kapsar) | SSH |
10.0.0.5:5432 | Yalnız o iç ağ arayüzünde | Veritabanı için doğru desen |
Geliştirme makinesinde çalışan bir ASP.NET Core uygulamasının "sunucuda erişilemez"
olmasının en sık nedeni, uygulamanın localhost'a bağlanmasıdır. Konteyner
dünyasında aynı tuzak daha da keskindir: konteynerin içinde
127.0.0.1'e bağlanan bir uygulamaya -p ile yayımlanan porttan
erişilemez, çünkü konteynerin kendi localhost'u vardır (7. ders). Kural:
ters vekil arkasındaysa 127.0.0.1, konteynerdeyse 0.0.0.0
dinlenir (ASPNETCORE_URLS=http://0.0.0.0:8080).
Arayüz ve yönlendirme bilgisi de iki komuttadır:
$ ip -brief a # arayüzler ve IP'ler (kısa)
lo UNKNOWN 127.0.0.1/8
eth0 UP 10.0.0.12/24
$ ip r # yönlendirme tablosu; "default via" = ağ geçidi
default via 10.0.0.1 dev eth0
$ ss -s # özet: kaç bağlantı, hangi durumda
$ ss -tan state time-wait | wc -l # TIME-WAIT yığılması (bağlantı sızıntısı belirtisi)
Bir kıdem detayı: 1024'ün altındaki portlara yalnız root (ya da
CAP_NET_BIND_SERVICE yetkisi verilmiş süreç) bağlanabilir. Bu yüzden
uygulamayı root olarak koşturmak yerine yüksek bir portta dinletip önüne 80/443'te duran
ters vekil koymak standart çözümdür — 3. dersin en az ayrıcalık ilkesiyle 08. bölümün
mimarisini birleştiren nokta budur.
03Ad çözümleme: hosts, DNS ve tuzakları
Bir ad IP'ye çevrilmeden hiçbir bağlantı kurulmaz ve bu çevirinin sırası
/etc/nsswitch.conf'ta tanımlıdır: önce /etc/hosts, sonra DNS.
/etc/hosts'a elle satır eklemek, DNS kaydı yayılmadan önce test etmenin ya da
bir ortamı yönlendirmenin en hızlı yoludur:
$ cat /etc/hosts
127.0.0.1 localhost
10.0.0.30 basvuru-db.kurum.local basvuru-db
$ cat /etc/resolv.conf # hangi DNS sunucusu kullanılıyor
nameserver 127.0.0.53 # systemd-resolved yerel önbelleği
$ resolvectl status # gerçek yukarı akış DNS sunucuları
Sorgu araçları arasındaki fark önemlidir ve çoğu yanlış teşhisin kaynağıdır:
| Araç | Neyi sorar | Ne zaman |
|---|---|---|
dig ad | Doğrudan DNS'e sorar, /etc/hosts'u atlar | DNS kaydı doğru mu; dig +short, dig @8.8.8.8 ad, dig ad TXT |
getent hosts ad | Sistemin gerçek çözümleme zincirini kullanır (hosts + DNS) | "Uygulama neyi görüyor" sorusunun doğru cevabı |
host / nslookup | DNS sorgusu (basit çıktı) | Hızlı bakış |
Tipik olay: dig doğru IP'yi döndürür ama uygulama başka yere bağlanır —
çünkü /etc/hosts'ta eski bir satır vardır ve dig onu görmez.
Teşhis getent hosts ad ile yapılır. İkinci tipik olay: DNS kaydı değişti ama
uygulama eski IP'yi kullanıyor — DNS önbelleği ve TTL. .NET tarafında bu, uzun ömürlü
HttpClient ile birlikte bilinen bir konudur:
SocketsHttpHandler.PooledConnectionLifetime ayarı DNS değişikliklerinin fark
edilmesi için vardır (5. hattaki HttpClient dersinin ağ tarafı).
04curl, nc ve "ping çalışıyor" yanılgısı
curl bir geliştiricinin en çok kullanacağı ağ aracıdır; 6. hattaki API
dersinde gördüğün istek/yanıt yapısını komut satırından kurar. Bilinmesi gereken
seçenekler:
$ curl -i https://api.kurum.gov.tr/basvuru/42 # -i: yanıt başlıklarını da yaz
$ curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://site.local/saglik
200 0.043s
$ curl -v https://site.local/api 2>&1 | head -30 # -v: TLS el sıkışması ve başlıklar
$ curl -X POST https://api.local/basvuru \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '"$TOKEN" \
-d '{"tcKimlik":"11111111111","sinavId":7}'
$ curl -H 'Host: basvuru.kurum.gov.tr' http://10.0.0.12/ # IP'ye git, sanal konağı test et
$ curl --resolve basvuru.kurum.gov.tr:443:10.0.0.12 https://basvuru.kurum.gov.tr/
$ curl -L http://site.local # yönlendirmeleri izle (HTTP→HTTPS testinde gerekli)
$ curl --cacert /usr/local/share/ca-certificates/kurum.crt https://ic-servis.local/
Üç ayrıntı kıdem farkı yaratır:
--resolveve-H 'Host: …'DNS'i atlayarak belirli bir sunucuyu test etmenin yoludur. Yayın öncesi "yeni sunucu doğru yanıt veriyor mu" sorusu, DNS'i değiştirmeden böyle cevaplanır.-H Hostyalnız HTTP'de yeter; HTTPS'te sertifika/SNI eşleşmesi için--resolvegerekir.- Çıkış kodları betikler için kritiktir:
0başarı,6ad çözülemedi (DNS),7bağlanılamadı (port/güvenlik duvarı),28zaman aşımı,35/60TLS sorunu (60= sertifika doğrulanamadı),22HTTP hata kodu (-file). Sağlık kontrol betiklerindecurl -fsS --max-time 5kalıbı kullanılır. -kyasaktır.-k/--insecuresertifika doğrulamasını kapatır; teşhis sırasında "sorun sertifikada mı" ayrımı için bir kez kullanılabilir ama çözüm asla değildir. Doğru çözüm kök sertifikayı sisteme tanıtmaktır (09. bölüm).
HTTP dışı portları sınamak için nc (netcat) ya da ss'nin
hedef tarafı kullanılır:
$ nc -zv 10.0.0.30 5432 # PostgreSQL portu açık mı
Connection to 10.0.0.30 5432 port [tcp/postgresql] succeeded!
$ nc -zv -w 3 db.local 5432 # 3 saniye zaman aşımıyla
$ timeout 3 bash -c '</dev/tcp/10.0.0.30/5432' && echo açık # nc yoksa: bash'in kendi yolu
"Ping çalışıyor ama uygulama açılmıyor" yanılgısı. ping ICMP
kullanır, TCP portlarıyla ilgisi yoktur: ping'in çalışması yalnız makinenin ağda olduğunu
söyler, servisin dinlediğini söylemez. Tersi de doğrudur — birçok kurumsal ağ ve bulut
sağlayıcısı ICMP'yi kapatır, bu yüzden ping'in çalışmaması makinenin erişilemez olduğu
anlamına gelmez. Aynı sınır traceroute için de geçerlidir: yol üstündeki
cihazlar yanıt vermeyebilir, çıktıdaki yıldızlar mutlaka arıza demek değildir. Doğru soru
her zaman "şu port üzerinden TCP bağlantısı kurulabiliyor mu"dur —
nc -zv veya curl.
Daha derin inceleme gerektiğinde paket yakalama devreye girer:
sudo tcpdump -i any -n port 5432 -c 20. Bu, "istek sunucuya hiç geliyor mu"
sorusunun kesin cevabıdır ve ters vekil hata ayıklamasında (08. bölüm) son çare olarak
kullanılır.
05Güvenlik duvarı ve teşhis merdiveni
Linux'ta paket süzme çekirdekteki netfilter altyapısıyla yapılır; gündelik
arayüzü Ubuntu'da ufw, RHEL ailesinde firewalld'dir (ikisi de
alttaki nftables/iptables kurallarını yazar).
# Ubuntu (ufw)
$ sudo ufw status verbose
$ sudo ufw default deny incoming && sudo ufw default allow outgoing
$ sudo ufw allow 22/tcp # SSH'ı AÇMADAN enable etmek seni dışarıda bırakır
$ sudo ufw allow 80,443/tcp
$ sudo ufw allow from 10.0.0.0/24 to any port 5432 proto tcp # yalnız iç ağa veritabanı
$ sudo ufw enable
# RHEL (firewalld)
$ sudo firewall-cmd --list-all
$ sudo firewall-cmd --permanent --add-service=https
$ sudo firewall-cmd --permanent --add-port=5080/tcp
$ sudo firewall-cmd --reload # --permanent olmadan verilen kural yeniden başlatmada kaybolur
İki uyarı: (1) Uzak bir sunucuda güvenlik duvarını etkinleştirmeden önce SSH
portuna izin ver — aksi hâlde bağlantın kopar ve konsola erişimin yoksa makineye giremezsin.
(2) firewalld'de --permanent yazmayı unutmak, kuralın yeniden
başlatmada kaybolmasına yol açar; tersi de olur: --permanent verip
--reload yapmamak kuralı hemen etkinleştirmez.
"Port açık ≠ servis dinliyor". Bu iki kavram ayrı katmandır ve karıştırılması
saatlerce yanlış yerde arama demektir. Güvenlik duvarında bir porta izin vermek, o portta
bir şey dinlediğini garanti etmez; tersine, servis dinliyor olsa da güvenlik duvarı
paketleri düşürüyorsa dışarıdan erişilemez. Ayırt etme yöntemi davranış farkıdır:
bağlantı hemen reddediliyorsa (Connection refused) genellikle o portta
dinleyen yoktur; bağlantı asılıp zaman aşımına düşüyorsa (timed out)
paket bir yerde düşürülüyor demektir — güvenlik duvarı, bulut güvenlik grubu ya da
yönlendirme.
Bu yüzden "uygulamaya erişemiyorum" sorunu her zaman şu merdivenle çözülür:
| # | Soru | Komut |
|---|---|---|
| 1 | Süreç ayakta mı? | systemctl status uygulama · journalctl -u uygulama -p err -n 50 (4. ders) |
| 2 | Doğru adres/portta dinliyor mu? | sudo ss -tulpn | grep 5080 |
| 3 | Makinenin kendisinden yanıt veriyor mu? | curl -i http://127.0.0.1:5080/saglik |
| 4 | Ters vekil doğru yere mi gönderiyor? | curl -i http://127.0.0.1/ · nginx -t · /var/log/nginx/error.log |
| 5 | Ağdan TCP kurulabiliyor mu? | Başka makineden nc -zv sunucu 443 — refused mı, timeout mu |
| 6 | Ad doğru çözülüyor, sertifika geçerli mi? | getent hosts ad · curl -v https://ad/ · openssl s_client |
Merdivenin değeri sırasındadır: içten dışa gidersin ve ilk başarısız basamak sorunun katmanını söyler. Kıdemli bir geliştiriciyi ayıran şey bu sıranın disiplinidir — 3. basamak çalışıyorsa uygulamada sorun yoktur, oradan sonrası altyapıdır.
06SSH: sunucuya güvenli erişim
SSH hem uzak kabuk hem dosya taşıma hem tünel aracıdır. 16-04'te Git için anahtar kullandık; 3. derste anahtar dosyalarının izinlerini gördük. Burada düzeni tamamlıyoruz.
$ ssh-keygen -t ed25519 -C "ayse@kurum" # ~/.ssh/id_ed25519 (+ .pub)
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub ayse@10.0.0.12
# ssh-copy-id yoksa: açık anahtarı sunucudaki ~/.ssh/authorized_keys dosyasına ekle (600 izinle)
$ ssh ayse@10.0.0.12
$ ssh -i ~/.ssh/ozel_anahtar -p 2222 ayse@sunucu
$ ssh sunucu 'systemctl is-active basvuru' # tek komut çalıştır (kabuk profili okunmaz — 2. ders)
~/.ssh/config gündelik kullanımı kısaltır ve unutulan parametreleri
ortadan kaldırır (izni 644, dizin 700):
Host basvuru-prod
HostName 10.0.0.12
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 60
Host db-prod
HostName 10.0.1.30
User ayse
ProxyJump basvuru-prod # atlama sunucusu üzerinden (bastion)
# Artık: ssh basvuru-prod · scp dosya basvuru-prod:/tmp/
ssh-agent, anahtar parolasını her bağlantıda yazmamak için anahtarı
bellekte tutar: eval "$(ssh-agent -s)" ve ssh-add ~/.ssh/id_ed25519.
Kıdem uyarısı: ForwardAgent yes (ajan yönlendirme) kullanışlıdır ama
bağlandığın sunucuda root yetkisi olan biri ajan soketin üzerinden senin kimliğinle
başka sistemlere bağlanabilir; güvenmediğin sunucuda açılmaz, yerine
ProxyJump kullanılır.
Sunucu tarafı sertleştirme (/etc/ssh/sshd_config, tercihen
/etc/ssh/sshd_config.d/ altında ek dosya) — 14. hattaki sunucu sertleştirme
maddelerinin en somut hâli:
PermitRootLogin no
PasswordAuthentication no # anahtar dışında girişi kapat (kaba kuvvet saldırısını bitirir)
KbdInteractiveAuthentication no
AllowGroups ssh-erisim # yalnız bu gruptaki kullanıcılar
X11Forwarding no
ClientAliveInterval 300
$ sudo sshd -t && sudo systemctl reload ssh # ÖNCE sözdizimi testi
Pazarlıksız sıra: parola girişini kapatmadan önce anahtarla girişin çalıştığını
ikinci bir oturumda doğrula. Açık oturumunu kapatmadan test etmek, kendini
sunucudan kilitlememenin tek güvencesidir. Ek katmanlar: fail2ban (tekrarlayan
başarısız denemede IP engelleme), SSH portunu değiştirmek (gürültüyü azaltır, güvenlik
sağlamaz), anahtarları donanım anahtarına bağlamak.
Tünel (port yönlendirme), dışarıya açılmayan bir servise güvenli erişimin standart yoludur:
$ ssh -L 15432:10.0.1.30:5432 basvuru-prod
# yerel 15432 → sunucudan db'nin 5432'sine. Artık kendi makinenden:
$ psql -h 127.0.0.1 -p 15432 -U basvuru
$ ssh -L 8080:127.0.0.1:5080 basvuru-prod # yalnız localhost'ta dinleyen uygulamayı görmek
$ ssh -J bastion ayse@10.0.1.30 # atlama sunucusu (ProxyJump'ın komut hâli)
Bu desen, veritabanını internete açmadan yönetmenin doğru yoludur: veritabanı yalnız iç ağda dinler, erişim SSH kimliğiyle denetlenir ve kayda geçer.
07Dosya taşıma: scp ve rsync
$ scp yayim.zip basvuru-prod:/tmp/ # tek dosya, hızlı ve basit
$ scp -r wwwroot basvuru-prod:/opt/basvuru/ # dizin
$ rsync -avz --delete ./yayim/ basvuru-prod:/opt/basvuru/ # DİKKAT: sonundaki eğik çizgi
$ rsync -avz --exclude '*.pdb' --exclude 'appsettings.Development.json' \
--chown=basvuru:basvuru ./yayim/ basvuru-prod:/opt/basvuru/
$ rsync -avzn --delete ./yayim/ basvuru-prod:/opt/basvuru/ # -n: DENEME, hiçbir şey yazmaz
rsync'in scp'ye üstünlüğü artımlı çalışmasıdır: yalnız değişen
parçaları taşır, izinleri ve zaman damgalarını korur (-a), sıkıştırır
(-z), ve --delete ile hedefi kaynakla birebir eşitler. İki kıdem
uyarısı: (1) Eğik çizgi tuzağı — kaynak/ "içeriğini kopyala",
kaynak "dizinin kendisini kopyala" demektir; karıştırınca
/opt/basvuru/yayim/yayim/ gibi bir yapı oluşur. (2) --delete
yıkıcıdır; her zaman önce -n (dry run) ile çalıştır ve çıktıda ne
silineceğini gör. Yayın betiklerinde bu sıra değişmez kuraldır.
Not: yayın (deployment) için doğru desen, dosyaları çalışan dizinin üzerine yazmak değil,
yeni bir sürüm dizinine açıp simgesel bağlantıyı çevirmektir (1. ders):
/opt/basvuru/releases/2026-09-17 + current bağlantısı. Geri alma,
bağlantıyı eski dizine döndürüp servisi yeniden başlatmaktan ibaret olur — 18. hattaki
"geri alma planı" maddesinin en yalın hâli. Uçtan uca yayın akışı 11. derste (dck-deploy)
konteynerle birlikte kurulur.
08nginx ters vekil: 13-02 ve 14-06 senedi
Uygulama neden doğrudan 80/443'te dinlemez? Beş gerekçe: (1) 1024 altı portlar root ister, uygulama root koşmamalıdır (3. ders); (2) TLS sertifikasını tek yerde yönetmek istersin; (3) aynı makinede birden çok uygulamayı ad/yola göre ayırman gerekir; (4) statik dosya sunumu, sıkıştırma, önbellek başlıkları ve hız sınırlama vekilin işidir; (5) yayın sırasında uygulamayı yeniden başlatırken vekil sabit kalır. Ters vekil (reverse proxy) bu katmandır: istemciden isteği alır, arkadaki uygulamaya iletir, yanıtı geri verir.
# /etc/nginx/sites-available/basvuru (RHEL'de /etc/nginx/conf.d/basvuru.conf)
server {
listen 80;
server_name basvuru.kurum.gov.tr;
return 301 https://$host$request_uri; # HTTP → HTTPS
}
server {
listen 443 ssl http2;
server_name basvuru.kurum.gov.tr;
ssl_certificate /etc/letsencrypt/live/basvuru.kurum.gov.tr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/basvuru.kurum.gov.tr/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
client_max_body_size 20m; # dosya yükleme sınırı (413 hatasının kaynağı)
location /saglik {
proxy_pass http://127.0.0.1:5080;
access_log off;
}
location / {
proxy_pass http://127.0.0.1:5080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade; # WebSocket/SignalR
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 120s;
}
}
$ sudo ln -s /etc/nginx/sites-available/basvuru /etc/nginx/sites-enabled/
$ sudo nginx -t && sudo systemctl reload nginx # ÖNCE test, sonra reload (kesintisiz)
Senet ödemesi: X-Forwarded-* ve ASP.NET Core. 13-02'de (HTTPS ve
yönlendirme) ve 14-06'da (ağ güvenliği) şu söz verilmişti: uygulama ters vekilin arkasında
çalışırken istemcinin gerçek IP'sini ve şemanın HTTPS olduğunu bilmesi gerekir.
Vekil bunu üç başlıkla söyler; uygulamanın bu başlıkları okuması için açıkça
yapılandırılması gerekir:
// Program.cs
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
o.KnownProxies.Add(IPAddress.Parse("127.0.0.1")); // yalnız güvenilen vekile inan
});
var app = builder.Build();
app.UseForwardedHeaders(); // pipeline'ın EN BAŞINDA olmalı
app.UseHttpsRedirection();
Bu yapılandırma yoksa dört somut arıza görülür: (1) log'larda ve hız sınırlamada bütün
isteklerin IP'si 127.0.0.1 görünür (14. hattaki iz kaydı gereksinimi çalışmaz);
(2) Request.Scheme http döner, bu yüzden uygulamanın ürettiği
yönlendirme ve mutlak bağlantılar http:// ile çıkar; (3) OAuth/OIDC akışında
redirect_uri yanlış şemayla üretilir ve sağlayıcı isteği reddeder (13. hat);
(4) UseHttpsRedirection sonsuz yönlendirme döngüsüne girer. Güvenlik
notu: bu başlıklar istemci tarafından uydurulabilir; bu yüzden
KnownProxies/KnownNetworks ile yalnız kendi vekiline güvenilir —
aksi hâlde IP tabanlı denetimler kandırılabilir.
Ters vekilin log'u ayrı dosyadadır (/var/log/nginx/access.log,
error.log) ve logrotate ile döner (4. ders). Teşhiste
error.log doğrudan cevap verir: connect() failed (111: Connection
refused) arkadaki uygulamanın ayakta olmadığını, upstream timed out ise
uygulamanın yavaş olduğunu söyler — merdivenin 3. ve 4. basamağını bir bakışta ayırır.
09TLS: sertifika almak, yenilemek ve güvenmek
13. hatta TLS'in nasıl çalıştığını, 14. hatta PKI ve güven zincirini gördük. Sunucu tarafında iş üç parçadır: sertifikayı almak, yenilemeyi otomatikleştirmek ve giden bağlantılarda karşı tarafa güvenmek.
Sertifika almak (Let's Encrypt). ACME protokolü, alan adının sahibi olduğunu doğrulayıp ücretsiz ve kısa ömürlü (90 gün) sertifika verir:
$ sudo apt install certbot python3-certbot-nginx
$ sudo certbot --nginx -d basvuru.kurum.gov.tr # nginx yapılandırmasını da düzenler
$ sudo certbot certonly --nginx -d basvuru.kurum.gov.tr # yalnız sertifika al, düzenleme yapma
$ sudo certbot renew --dry-run # yenileme mekanizmasını sına
$ systemctl list-timers | grep certbot # yenileme bir systemd timer'dır (4. ders)
Kıdem notları: (1) Yenileme otomatik olmalı — 90 günlük sertifikanın elle
yenilenmesi unutulur ve sonuç tam bir kesintidir; certbot bunu timer ile yapar, ama
yenileme sonrası nginx'in reload edilmesi gerekir
(--deploy-hook "systemctl reload nginx"). (2) HTTP-01 doğrulaması 80 portunun
dışarıdan erişilebilir olmasını ister; kapalı ağlarda DNS-01 doğrulaması kullanılır.
(3) Let's Encrypt'in hız sınırı vardır, deneme yaparken --dry-run ya
da staging ortamı kullanılır. (4) Kamu ve kurum içi sistemlerde sertifika genelde kurumun
kendi CA'sından ya da nitelikli bir sağlayıcıdan gelir; o durumda fullchain
dosyasının ara sertifikaları içerdiğinden emin olunur — eksik ara sertifika,
tarayıcıda çalışıp mobil istemcide ya da .NET'te "güven zinciri kurulamadı" hatası veren
klasik bir arızadır. Doğrulama:
$ openssl s_client -connect basvuru.kurum.gov.tr:443 -servername basvuru.kurum.gov.tr < /dev/null \
| openssl x509 -noout -subject -issuer -dates
$ curl -vI https://basvuru.kurum.gov.tr 2>&1 | grep -E 'subject|issuer|expire'
Giden bağlantılarda güven. Sunucundaki uygulama başka bir HTTPS servisine bağlanırken karşı tarafın sertifikasını sistem güven deposuna göre doğrular. Kurum içi bir servis ya da TLS'i sonlandıran bir kurumsal vekil söz konusuysa kök sertifikanın sisteme tanıtılması gerekir (4. dersteki paket yöneticisi örneğinin aynısı, bu kez uygulaman için):
$ sudo cp kurum-koku.crt /usr/local/share/ca-certificates/kurum-koku.crt
$ sudo update-ca-certificates # Debian/Ubuntu
# RHEL: /etc/pki/ca-trust/source/anchors/ + sudo update-ca-trust
$ curl https://ic-servis.kurum.local/saglik # -k OLMADAN çalışmalı
Bu adım 14-04'ün PKI senedini ve 16-04'ün "vekil arkasında sertifika hatası"
notunu kapatır. .NET, Linux'ta bu sistem deposunu kullanır (Windows'un mağazasını
değil), bu yüzden HttpClient'ta sertifika doğrulamasını devre dışı bırakan
geri çağırmalar yazmak yerine kök sertifikayı tanıtmak doğru çözümdür.
Konteynerdeki hâli 8. derste karşına çıkacak: imaj kendi güven deposunu taşır,
kök sertifika Dockerfile'da eklenmeli — aksi hâlde uygulama hostta çalışıp
konteynerde TLS hatası verir.
10Mini alıştırma — erişimi uçtan uca kur ve teşhis et
Yaklaşık 20 dakika · bir Linux kabuğu (WSL2 ya da sanal makine); nginx kurulu olsun
- Dinleyenleri oku.
sudo ss -tulpnçalıştır; her satır için "hangi süreç, hangi adres, dışarıya açık mı" sorularını cevapla. Sonraip -brief aveip rile makinenin IP'sini ve ağ geçidini yaz. - Basit sunucu.
python3 -m http.server 8080 --bind 127.0.0.1başlat. Başka bir terminaldecurl -i http://127.0.0.1:8080/çalışıyor mu? Makinenin kendi IP'siyle (curl -i http://10.x.x.x:8080/) dene — neden başarısız? Sunucuyu--bind 0.0.0.0ile yeniden başlatıp yinele. - refused ↔ timeout.
nc -zv 127.0.0.1 9999(dinleyen yok) venc -zv -w 3 10.255.255.1 80(erişilemez adres) çalıştır. İki hata mesajını ve hangi katmanı işaret ettiklerini yaz. - Ad çözümleme.
/etc/hosts'a127.0.0.1 basvuru.testsatırını ekle.getent hosts basvuru.testvedig +short basvuru.testçıktılarını karşılaştır; farkı bir cümleyle açıkla. - Ters vekil. nginx'te
basvuru.testiçin birserverbloğu yaz;location /içindeproxy_pass http://127.0.0.1:8080;ve dörtX-Forwarded-*/Hostbaşlığını kur.sudo nginx -tile doğrula, reload et,curl -i http://basvuru.test/ile test et. - Başlıkları gör. Python sunucusunu kapat; yerine gelen başlıkları yazdıran bir
şey koy (ör.
nc -l 8080ile tek istek dinleyipcurlat) veX-Forwarded-ForileX-Forwarded-Protobaşlıklarının geldiğini gözle doğrula. - Merdiven provası. nginx'te
proxy_passportunu kasıtlı yanlış yaz (8081), reload et,curl -i http://basvuru.test/çalıştır vesudo tail -5 /var/log/nginx/error.logçıktısını oku. Hangi mesajın hangi basamağı gösterdiğini yaz, sonra düzelt. - SSH.
ssh-keygen -t ed25519 -f ~/.ssh/deneme -N ""ile anahtar üret;~/.ssh/config'e birHostbloğu yaz (HostName localhost,IdentityFile,IdentitiesOnly yes). Mümkünse açık anahtarı~/.ssh/authorized_keys'e ekleyipssh deneme-hostile bağlan; izinleri700/600yapmayı unutma (3. ders).
11Kendini sına
ss -tulpn çıktısı nasıl okunur; 127.0.0.1 ile 0.0.0.0 dinlemek arasındaki fark nedir ve hangi durumda hangisi seçilir?
-t TCP, -u UDP, -l yalnız
dinleyenler, -p süreç (sudo gerekir), -n çözümleme yapmaz.
Çıktıda hangi sürecin hangi adres:portta dinlediği görünür.
127.0.0.1:5080 yalnız makinenin içinden erişilebilir demektir — ters vekil
arkasındaki uygulama için doğru ve kasıtlı seçim, çünkü uygulama internete
doğrudan açılmaz. 0.0.0.0:80 bütün arayüzlerde dinler; ters vekil ve
güvenlik duvarıyla korunur. Belirli bir iç ağ arayüzüne bağlamak
(10.0.0.5:5432) veritabanı için doğru desendir. Kural: ters vekil
arkasındaysa 127.0.0.1, konteyner içindeyse 0.0.0.0 dinlenir —
konteynerin kendi localhost'u olduğu için 127.0.0.1'e bağlanan
uygulamaya yayımlanan porttan erişilemez. Ek olarak 1024 altındaki portlara yalnız root
ya da CAP_NET_BIND_SERVICE yetkili süreç bağlanabilir; bu yüzden uygulama
yüksek portta koşar, 80/443'te ters vekil durur. Arayüz/rota bilgisi
ip -brief a ve ip r ile, bağlantı özeti ss -s ile
alınır.
dig ile getent hosts arasındaki fark nedir? curl'ün hangi seçenekleri ve çıkış kodları teşhiste kullanılır; ping neden yeterli bir kanıt değildir?
dig doğrudan DNS sunucusuna sorar ve
/etc/hosts'u atlar; getent hosts ad ise sistemin gerçek
çözümleme zincirini (nsswitch.conf: hosts → DNS) kullanır, yani uygulamanın
göreceği cevabı verir. "dig doğru IP diyor ama uygulama başka yere bağlanıyor" olayının
nedeni genelde /etc/hosts'taki eski satırdır; DNS değişti ama uygulama eski
IP'yi kullanıyorsa neden önbellek/TTL'dir (.NET'te
PooledConnectionLifetime bu yüzden ayarlanır). curl:
-i yanıt başlıkları, -v TLS el sıkışması ve istek detayı,
-H başlık, -d gövde, -X yöntem, -L
yönlendirme izleme, -w '%{http_code} %{time_total}' ölçüm,
--resolve ad:443:IP DNS'i atlayıp belirli sunucuyu test etme (HTTPS'te
-H Host yetmez, SNI/sertifika eşleşmesi için --resolve gerekir).
Çıkış kodları: 0 başarı, 6 DNS, 7 bağlanamadı, 28 zaman aşımı, 60 sertifika
doğrulanamadı, -f ile 22 HTTP hatası; sağlık kontrolünde
curl -fsS --max-time 5 kalıbı kullanılır. -k yalnız "sorun
sertifikada mı" ayrımı için geçici teşhis aracıdır, çözüm değildir. ping
ICMP kullanır: çalışması yalnız makinenin ağda olduğunu gösterir, portta bir servis
dinlediğini göstermez; ayrıca birçok kurumsal ağ ve bulut ICMP'yi kapattığı için ping'in
çalışmaması erişilemezlik kanıtı değildir. Doğru soru "şu portta TCP kurulabiliyor mu"dur:
nc -zv host port ya da curl; daha derinde
tcpdump.
"Port açık ≠ servis dinliyor" ne demektir; Connection refused ile timed out farkı neyi gösterir? "Uygulamaya erişemiyorum" sorununu hangi sırayla teşhis edersin?
Güvenlik duvarında bir porta izin vermek, o portta bir şeyin
dinlediğini garanti etmez; tersine servis dinlerken güvenlik duvarı paketleri düşürüyorsa
dışarıdan erişilemez — bunlar ayrı katmanlardır. Davranış farkı ayırt ettirir:
Connection refused paket hedefe ulaştı ama o portta dinleyen yok
(ya da açıkça reddedildi); timed out paket bir yerde sessizce
düşürülüyor — güvenlik duvarı, bulut güvenlik grubu ya da yönlendirme sorunu. Teşhis
merdiveni içten dışa doğrudur: (1) süreç ayakta mı
(systemctl status, journalctl -u … -p err); (2) doğru
adres/portta dinliyor mu (ss -tulpn); (3) makinenin kendisinden yanıt
veriyor mu (curl -i http://127.0.0.1:port/saglik) — buraya kadar
çalışıyorsa uygulama sağlamdır, sorun altyapıdadır; (4) ters vekil doğru yere mi
gönderiyor (nginx -t, /var/log/nginx/error.log: "connect()
failed (111: Connection refused)" arkadaki uygulamanın kapalı olduğunu, "upstream timed
out" yavaşlığı gösterir); (5) ağdan TCP kurulabiliyor mu, refused mı timeout mu
(nc -zv); (6) ad doğru çözülüyor ve sertifika geçerli mi
(getent hosts, curl -v, openssl s_client).
Güvenlik duvarı yönetiminde iki tuzak: uzak sunucuda ufw enable öncesi SSH
portuna izin vermemek bağlantıyı koparır; firewalld'de
--permanent ve --reload ikilisini atlamak kuralı ya geçici ya
etkisiz bırakır.
Ters vekil neden kullanılır; X-Forwarded-For/-Proto ile ASP.NET Core ForwardedHeaders ilişkisi nedir? TLS sertifikası nasıl alınır/yenilenir ve kurum kök sertifikası neden sisteme tanıtılır?
Ters vekil beş nedenle kullanılır: 1024 altı portlar root ister
(uygulama root koşmamalı), TLS tek yerde yönetilir, aynı makinede birden çok uygulama
ad/yola göre ayrılır, statik dosya/sıkıştırma/önbellek/hız sınırı vekilin işidir, ve
yayın sırasında uygulama yeniden başlarken vekil sabit kalır. nginx'te
proxy_pass http://127.0.0.1:5080 ile birlikte Host,
X-Real-IP, X-Forwarded-For, X-Forwarded-Proto
başlıkları kurulur; WebSocket/SignalR için Upgrade/Connection
eklenir; değişiklik nginx -t ile doğrulanıp reload edilir.
Uygulamanın bu başlıkları okuması için yapılandırılması gerekir:
ForwardedHeadersOptions + app.UseForwardedHeaders()
(pipeline'ın en başında) ve KnownProxies ile yalnız kendi vekiline güven —
başlıklar istemci tarafından uydurulabildiği için bu sınırlama güvenlik gereğidir. Eksikse:
bütün istekler 127.0.0.1'den görünür (iz kaydı ve hız sınırı bozulur),
Request.Scheme http döner, üretilen mutlak bağlantılar ve OIDC
redirect_uri yanlış şemayla çıkar, UseHttpsRedirection sonsuz
döngüye girer. TLS: Let's Encrypt/certbot ile ACME doğrulamasıyla 90 günlük sertifika
alınır, yenileme systemd timer'ıyla otomatiktir ve
--deploy-hook "systemctl reload nginx" ile vekil tazelenir; HTTP-01 için 80
portu erişilebilir olmalı, kapalı ağda DNS-01 kullanılır. Kurum/kamu sertifikalarında
fullchain'in ara sertifikaları içermesi şarttır — eksikse tarayıcıda çalışıp
.NET/mobil istemcide "güven zinciri kurulamadı" hatası verir
(openssl s_client ile doğrulanır). Giden bağlantılarda karşı tarafa güvenmek
için kurum kök sertifikası /usr/local/share/ca-certificates/ +
update-ca-certificates (RHEL: anchors + update-ca-trust) ile
sisteme tanıtılır; .NET Linux'ta bu sistem deposunu kullanır, doğrulamayı kapatan geri
çağırma yazmak yanlış çözümdür. Konteynerde imaj kendi güven deposunu taşıdığı için aynı
kurulum Dockerfile'da yapılmalıdır.
12Sınav köşesi
Yazılıda nasıl sorulur
(1) IP adresi, port ve soket kavramlarını açıklayınız. (2) Bir sunucuda dinlemede
olan portlar hangi komutla listelenir? (3) /etc/hosts dosyası ne işe yarar;
ad çözümleme sırası nasıldır? (4) curl komutu ile bir API'ye POST isteği
nasıl gönderilir? (5) ping komutunun çalışması servisin erişilebilir
olduğunu kanıtlar mı; gerekçelendiriniz. (6) Güvenlik duvarında bir portu açma komutunu
yazınız (ufw veya firewalld). (7) SSH ile anahtar tabanlı
kimlik doğrulama nasıl kurulur; parola girişi neden kapatılır? (8) SSH port yönlendirme
(tünel) hangi amaçla kullanılır? (9) Ters vekil (reverse proxy) nedir; hangi avantajları
sağlar? (10) TLS sonlandırma nedir; X-Forwarded-Proto başlığı ne işe yarar?
(11) scp ile rsync arasındaki fark nedir?
Sözlüde nasıl anlatılır — 2 dakikalık taslak
Düzen: (1) Teşhis disiplini — "'Erişemiyorum' dendiğinde içten dışa giderim: servis
ayakta mı, doğru portta dinliyor mu (ss -tulpn), makinenin kendisinden
curl 127.0.0.1 yanıt veriyor mu, vekil doğru yere mi gönderiyor, ağdan TCP
kurulabiliyor mu, ad ve sertifika doğru mu. İlk başarısız basamak katmanı söyler."
(2) Ayrım bilgisi — "Ping ICMP'dir, port kanıtı değil; refused dinleyen
yok demek, timeout paketin düşürüldüğünü gösterir. Güvenlik duvarında port
açık olması servisin dinlediği anlamına gelmez." (3) Üretim düzeni — "Uygulamayı
127.0.0.1'de yüksek portta koştururum, önünde nginx: TLS sonlandırma,
HTTP→HTTPS yönlendirme, statik dosya ve hız sınırı orada.
X-Forwarded-For/-Proto başlıklarını geçirir, uygulamada
UseForwardedHeaders ile KnownProxies vererek okurum — yoksa
log'da gerçek IP kaybolur ve OIDC yönlendirmesi yanlış şemayla üretilir."
(4) Erişim ve taşıma — "Sunucuya yalnız anahtarla SSH; PermitRootLogin no,
PasswordAuthentication no — ama parolayı kapatmadan önce ikinci bir
oturumda anahtarla girişi doğrularım. Veritabanına ssh -L tüneliyle
erişirim, internete açmam. Dosya taşımada rsync -avz, --delete
öncesi mutlaka -n denemesi." (5) Kıdem vuruşu — "Sertifikada
-k ile ilerlemem: kurum kök sertifikasını
update-ca-certificates ile sisteme tanıtırım; fullchain'de ara
sertifika eksikse tarayıcı çalışır, .NET istemcisi hata verir — bunu
openssl s_client ile doğrularım. Let's Encrypt yenilemesini timer'a bağlar,
--deploy-hook ile nginx'i reload ettiririm."
