Sunucu & Performans

Virtualmin Sunucu Sıkılaştırma: Webmin Güvenlik Rehberi

Virtualmin Sunucu Sıkılaştırma: Webmin Güvenlik Rehberi

Kısa cevap: Bir Webmin/Virtualmin sunucusunu güvenli kılan tek bir ayar yoktur; katmanlı sıkılaştırmadır: paneli ve SSH’ı dışarıya kapatmak, TLS’i modernleştirmek, güvenlik header’larını eklemek, hafif bir istek filtresi (WAF-lite) koymak, Fail2ban ile brute-force’u kesmek ve çekirdeği sertleştirmek. Asıl ustalık ise hiçbir sitenin patlamamasıdır — yani önbelleği varsayılan kapalı tutup CMS oturum çerezlerini doğru atlamak. Aşağıda üretimde uyguladığımız akışı, doğrulanmış örneklerle anlatıyoruz.

Webmin ve onun hosting katmanı Virtualmin, sunucuyu tarayıcıdan yönetmenin en pratik yollarından biri. Ama bir kontrol paneli aynı zamanda bir saldırı yüzeyidir: 10000 portu, SSH, e-posta servisleri, PHP-FPM havuzları… Hepsi yanlış yapılandırılırsa kapı olur. Webmin’in ne olduğunu Webmin nedir yazımızda anlattık; bu yazıda işin asıl zor kısmına giriyoruz: onu üretimde nasıl güvenli hale getiriyoruz?

Aşağıdaki örnekler AlmaLinux/Rocky + Nginx tabanlı bir Virtualmin kurulumundan; ama prensipler her dağıtımda geçerli.

Katman 1 — Erişimi kıs: panel ve SSH

İlk kural: yönetim arayüzlerini herkese açık bırakma. Webmin varsayılan olarak 10000 portundan yayın yapar — bunu tüm internete açmak yerine güvenlik duvarıyla yalnız güvenilir IP’lere veya bir VPN’e kısıtlayın.

firewalld — Webmin portunu kısıtla
# 10000'i herkese açmak yerine yalnız ofis IP'sine izin ver
sudo firewall-cmd --permanent --remove-port=10000/tcp
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" 
  source address="203.0.113.10/32" port port="10000" protocol="tcp" accept'
sudo firewall-cmd --reload

SSH tarafında en sık yapılan hata, root + parola girişini açık bırakmaktır. Doğrusu anahtar tabanlı kimlik doğrulamaya geçip parolayı tamamen kapatmaktır:

/etc/ssh/sshd_config.d/90-hardening.conf
# ÖNCE anahtarınızı yükleyin (ssh-copy-id), SONRA bunları uygulayın:
PermitRootLogin prohibit-password
PasswordAuthentication no
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2

# Modern algoritmalar
KexAlgorithms curve25519-sha256,[email protected]
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
Sıra çok önemli

Parola girişini kapatmadan önce SSH anahtarınızın çalıştığını mutlaka doğrulayın. Aksi halde kendinizi sunucudan kilitlersiniz. Bir terminal oturumunu açık tutarken ikinci bir oturumla test edin.

Katman 2 — TLS’i modernleştir

Eski/varsayılan yapılandırmalar hâlâ TLS 1.0/1.1’e ve zayıf şifrelere izin verebilir. Modern bir taban yalnızca TLS 1.2 ve 1.3’tür:

/etc/nginx/snippets/ssl-hardening.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
ssl_ecdh_curve X25519:secp384r1;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
HSTS ve preload: dikkatli olun

HSTS güçlü bir korumadır ama preload kalıcı bir taahhüttür. Resmî preload listesinin uyardığı gibi, listeye girince tüm site ve alt alan adlarınız tarayıcıda zorla HTTPS’e kilitlenir; HTTP’ye dönmeniz gerekirse erişim kesilir. Çok-kiracılı bir panelde HTTP-only bir alt alan adı varsa includeSubDomains; preload onu kırar. Emin değilseniz preload‘suz başlayın:

HSTS — temkinli başlangıç
# Önce preload'suz, kısa süreyle test edin:
add_header Strict-Transport-Security "max-age=31536000" always;
# Tüm subdomain'lerde HTTPS garantiliyse includeSubDomains ekleyin.
# preload'u ancak kalıcı olarak taahhüt edebiliyorsanız ekleyin.

Katman 3 — Güvenlik header’ları (ve sessiz tuzak)

OWASP Secure Headers tabanlı minimum set:

/etc/nginx/snippets/security-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Content-Security-Policy site bazında özelleştirilmeli — global CSP çoğu siteyi kırar.

Not: X-XSS-Protection başlığını eklemeyin. MDN ve OWASP’a göre bu başlık modern tarayıcılarda kullanımdan kaldırıldı ve bazı durumlarda yeni güvenlik sorunları doğurabiliyor; yerine Content-Security-Policy kullanılır.

Nginx add_header kalıtım tuzağı

Burada çoğu kişinin düştüğü sessiz bir hata var: Nginx’te bir location bloğu kendi add_header‘ını tanımlarsa, üst bloktaki tüm header’ları devralmaz, sıfırlar — uyarı ya da log olmadan. Yani statik dosyalara Cache-Control eklediğiniz bir location içinde güvenlik header’larınız sessizce kaybolur. Çözüm: header snippet’ini ilgili location içine tekrar include edin, ya da Nginx 1.29.3+ ise add_header_inherit merge; kullanın.

Katman 4 — Hafif istek filtresi (WAF-lite)

Tam bir ModSecurity + OWASP CRS kurmak Nginx’i yeniden derlemeyi gerektirir ve Virtualmin’in kurulumunu bozabilir. Daha hafif, sıfır-derleme bir yaklaşım: bilinen tarayıcı/bot imzalarını ve kaba kötücül URI desenlerini map ile yakalayıp reddetmek.

http {} — bad bot / bad URI map
map $http_user_agent $bad_bot {
    default 0;
    "~*(?:nikto|nmap|masscan|sqlmap|zmeu|semrush|mj12bot)" 1;
}
map $request_uri $bad_uri {
    default 0;
    "~*(?:etc/passwd|proc/self/environ)" 1;
    "~*(?:.(?:git|env|htpasswd|sql|bak|swp)(?:?|$))" 1;
}
# vhost içinde:
# if ($bad_bot) { return 403; }
# if ($bad_uri) { return 403; }
Dürüst olalım: bu “lite”dır, tam WAF değil

Bu filtre ucuz ve etkili bir ilk savunmadır ama sihir değildir. $request_uri URL-decode edilmeden eşleştiği için %2e%2e%2f gibi kodlanmış payload’lar bu desenleri atlayabilir. Amaç gürültüyü ve otomatik tarayıcıları kesmek; gerçek bir WAF ihtiyacı varsa ayrı bir katman (ör. Cloudflare veya derlenmiş ModSecurity) gerekir. Ayrıca desenleri fazla agresif tutarsanız meşru istekleri de bloklarsınız — denge şart.

Katman 5 — Fail2ban ile brute-force’u kes

Header ve filtre statik korumadır; Fail2ban ise davranışsal korumadır: tekrarlayan başarısız denemeleri yapan IP’leri geçici banlar. Webmin paneli dahil kritik jail’ler:

/etc/fail2ban/jail.local
[sshd]
enabled  = true
maxretry = 3
bantime  = 7200

[webmin-auth]
enabled  = true
port     = 10000
logpath  = /var/webmin/miniserv.log
maxretry = 3
bantime  = 7200

[nginx-http-auth]
enabled = true
Log yolunu doğrulayın, yoksa koruma “sahte” olur

Virtualmin, alan adı başına ayrı Nginx logları tutabilir (ör. /var/log/virtualmin/ veya kullanıcı ana dizini altında). Jail’lerin logpath‘i gerçek log konumunu göstermiyorsa, Fail2ban boş bir dosyayı izler ve koruma var sanırsınız ama yoktur. Kurduktan sonra fail2ban-client status <jail> ile gerçekten satır okuduğunu doğrulayın. Ayrıca 404/403’e dayalı bot jail’lerini fazla agresif kurarsanız meşru kullanıcıları banlama riski vardır.

Katman 6 — Çekirdek ve güvenlik duvarı

Son katman işletim sistemi düzeyinde: SYN-flood koruması, ters yol filtreleme ve makul ağ limitleri.

/etc/sysctl.d/99-server-tuning.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.core.somaxconn = 65535

En kritik tuzak: önbellek bir siteyi nasıl patlatır?

Performans için tam sayfa önbelleği (FastCGI cache) cazip. Ama panel kurulumlarında en tehlikeli hata, önbelleği varsayılan olarak tüm sitelerde açmaktır. Çünkü her CMS oturumu farklı bir çerezle taşır; bunları atlamazsanız, oturum açmış bir kullanıcıya başka birinin önbelleklenmiş sayfası gösterilebilir. E-ticarette bu, sepet/ödeme bilgisinin sızması demektir.

Bu yüzden bizde kural nettir: önbellek varsayılan kapalı, alan adı bazında açılır ve tüm büyük CMS çerezleri tek bir bypass listesinde toplanır. Sık unutulan çerezler:

CMS / Framework Atlanması gereken oturum çerezi
WordPress wordpress_logged_in_*, wp-postpass
WooCommerce woocommerce_cart_hash, woocommerce_items_in_cart
Joomla joomla_user_state
Drupal SESS*, Drupal.visitor*
PrestaShop PrestaShop-*
Magento frontend, adminhtml
Laravel / genel PHP laravel_session, PHPSESSID

Listeden bir CMS’i unutmak (ör. Joomla, PrestaShop) tam da “site patlatan” senaryodur. Bilinmeyen bir framework için en güvenli varsayılan, o site için önbelleği hiç açmamaktır.

“Site patlatmama” prensibi

Sıkılaştırmanın gerçek sınavı, bilinmeyen bir siteyi kırmadan koruyabilmektir. Bizim global kuralımız: hiçbir genel ayar, tanımadığımız bir CMS’i bozmamalı. Pratikte bu şu demek — önbellek kapalı başlar, body limitleri makul (64MB), PHP zaman aşımı makul (120s), güvenlik kuralları yanlış-pozitif üretmeyecek kadar temkinli. CMS’e özel agresif optimizasyonlar ise isteğe bağlı snippet olarak, ancak o site için açıkça etkinleştirilir.

DKS pratikte ne yapıyor?

Bir sunucuyu devraldığımızda ya da sıfırdan kurduğumuzda bu katmanları tek tek, idempotent (tekrar çalıştırılabilir) bir akışla uyguluyoruz: erişim kısıtlama, TLS, header’lar, WAF-lite, Fail2ban, sysctl ve önbellek hijyeni. Her adımı kurulumdan sonra doğruluyoruz — Fail2ban gerçekten log okuyor mu, nginx -t temiz mi, header’lar location’larda kayboluyor mu. Sunucu kurulumu ve altyapı tarafında işin bu kısmını standart hale getirdik; sürekli güncelleme ve izleme yükünü ise bakım hizmetimizde üstleniyoruz.

Çünkü güvenlik tek seferlik bir kurulum değil, sürdürülen bir disiplindir: bir kez sıkılaştırılmış ama altı ay güncellenmemiş sunucu, hiç dokunulmamış olandan çok da güvenli değildir.

Sıkça sorulan sorular

Webmin’i internete açık bırakmak güvenli mi?

Önerilmez. Webmin’in 10000 portunu tüm internete açmak yerine güvenlik duvarıyla yalnız güvenilir IP’lere veya bir VPN’e kısıtlamak gerekir. Buna ek olarak SSL, iki adımlı doğrulama ve Fail2ban’ın webmin-auth jail’i ile panel brute-force denemeleri kesilir.

SSH’ta root parola girişini kapatmalı mıyım?

Evet. En güvenli yapı anahtar tabanlı kimlik doğrulamadır: önce SSH anahtarınızı yükleyip çalıştığını doğrulayın, sonra PasswordAuthentication’ı kapatıp PermitRootLogin’i prohibit-password yapın. Sırayı atlarsanız kendinizi sunucudan kilitleyebilirsiniz.

Fail2ban kurmak yeterli mi?

Tek başına değil. Fail2ban brute-force’u azaltır ama jail’lerin doğru log dosyalarını izlemesi şarttır; yanlış logpath ile koruma sadece kâğıt üzerinde kalır. Kurulumdan sonra fail2ban-client status ile gerçekten satır okuduğunu doğrulayın ve TLS, header, WAF gibi diğer katmanlarla birlikte kullanın.

Tam sayfa önbelleği neden varsayılan kapalı olmalı?

Çünkü her CMS oturumu farklı bir çerezle taşınır. Önbelleği tüm sitelerde körlemesine açarsanız ve oturum çerezlerini atlamazsanız, giriş yapmış bir kullanıcıya başkasının önbelleklenmiş sayfası gösterilebilir; e-ticarette sepet/ödeme bilgisi sızabilir. Önbellek alan adı bazında, çerez bypass listesi tamamlanarak açılmalıdır.

ModSecurity yerine Nginx map tabanlı filtre yeterli mi?

Gürültüyü ve otomatik tarayıcıları kesmek için iyi bir ilk savunmadır ve derleme gerektirmez. Ancak URL-encoded payload’ları atlayabildiği için tam bir WAF’ın yerini tutmaz. Gerçek WAF ihtiyacı olan sitelerde Cloudflare veya derlenmiş ModSecurity gibi ayrı bir katman gerekir.

Sunucunuz gerçekten sıkılaştırılmış mı?

Mevcut Webmin/Virtualmin kurulumunuz bu katmanların hangilerini geçiyor — SSH, TLS, header’lar, Fail2ban, önbellek hijyeni? Bizimle iletişime geçin; sunucunuzu denetleyip somut bir sıkılaştırma yol haritası çıkaralım.

Digital Kernel Software

Digital Kernel Software

WordPress tema geliştirme, hosting ve sunucu performansı üzerine yazıyoruz. İstanbul / Türkiye.

Projeniz mi var?

Okuduğunuzu
projenize uygulayalım

WordPress, hosting ve performans tarafında yazdıklarımızı sizin işinizde hayata geçirelim. Net konuşuruz — pazarlama dili değil, uygulanabilir çözüm.