---
title: "Self-Hosted Mail Sunucusu: Sıfırdan Adım Adım Kurulum Rehberi"
slug: "027-self-hosted-mail-sunucusu-sifirdan-kurulum-rehberi"
date: "2026-08-01"
category: "linux"
tags: ["linux", "devops", "security"]
excerpt: "Hetzner VPS üzerinde Postfix, Dovecot, OpenDKIM, SpamAssassin ve Roundcube ile sıfırdan kendi mail sunucunuzu kurun. Adım adım, karşılaşılan hatalarla birlikte kapsamlı bir rehber."
coverImage: "/images/posts/027-self-hosted-mail-sunucusu-sifirdan-kurulum-rehberi/cover.webp"
author: "Erhan ÜRGÜN"
seoTitle: "Self-Hosted Mail Sunucusu: Sıfırdan Adım Adım Kurulum Rehberi"
seoDescription: "Hetzner VPS üzerinde Postfix, Dovecot, OpenDKIM, SpamAssassin ve Roundcube ile sıfırdan kendi mail sunucunuzu kurun. Adım adım, karşılaşılan hatalarla birlikte kapsamlı bir rehber."
featured: false
published: true
---
**Postfix + Dovecot + OpenDKIM + SpamAssassin + Roundcube | Hetzner VPS | Ubuntu 24.04**
Yıllardır hosting kullanıyordum; cPanel'e aşinaydım, mail ihtiyacımı da hosting üzerinden karşılıyordum... Ama bi noktadan sonra artık hosting yaptığım işler için yetersiz kaldığından, hosting'ten vazgeçip VPS sunucusuna geçmiştim (son 2-3 yıldır aktif olarak VPS sunucusu kullanıyorum)... Performans, esneklik, kontrol vb herşey hosting'e göre çok daha iyi ama benim için tek bir eksiklik vardı, o da mail...
Hosting'te mail hazır geliyordu. Sunucuya geçince bu lüks ortadan kalktı. "Gmail kullanırım, Hotmail kullanırım" dedim bir süre. Ama Google'ın her mailinizi okuduğunu, reklamlar için profillediğini biliyorsunuz. Microsoft da farklı değil. Üstelik bir gün bir "politika ihlali" bahanesiyle hesabınızı kapatabilirler - yıllarca biriktirdiğiniz mailler, kişisel yazışmalarınız, iş bağlantılarınız hep bir anda yok olabilir. Bu bağımlılık hissi giderek rahatsız etmeye başladı.
Aslında hosting'ten sunucuya geçtiğim günden bu yana kendi mail sunucumu kurmak istiyordum. Ama açıkçası bir tık meşakkatli bir iş olduğundan pek yanaşamıyordum... Birkaç defa denedim, birkaç defa sunucuyu bozabilecek duruma soktum, birkaç defa "sonra yaparım" deyip erteledim. İlk denemelerimden birinde [MailCow](https://mailcow.email)'u kurdum - tam kapsamlı, Docker tabanlı bir mail çözümü. Ama MailCow'un yapısı benim için fazla karmaşıktı: onlarca container, kendi DNS resolver'ı, kendi web arayüzü... "Ben sadece mail göndermek istiyorum" diye düşünürken kendimi dev bir altyapıyı yönetirken buldum. Sevmedim, ısınamadım ve bu yüzden kaldırmak zorunda kaldım...
Aradan baya bi zaman geçti, yine mail ihtiyacı doğdu ve sonra aklıma geldi ki: cPanel'den, Plesk'ten zaten aşina olduğum bir şey vardı - [Roundcube](https://github.com/roundcube/roundcubemail-docker). Sade, hafif, açık kaynak. İhtiyacım olan tam olarak buydu. Ve artık bu işi daha fazla erteleyemezdim, ihtiyaç giderek artıyordu ve bugüne nasip oldu şükür... :) (Bugün dediğime bakmayın bu rehber üzerinden aylardır uğraşıyordum fakat bitirememiştim!)
Bu rehberde, Hetzner VPS üzerinde sıfırdan tam kapsamlı bir mail sunucusu kurulumunu anlatıyorum. Adım adım, karşılaştığım her hatayla birlikte... Şimdii çayınızı/kahvenizi hazır edin, arkanıza yaslanın çünkü uzuuunca bi rehber olacak. En az bi nereden baksanız 1-3 saatinizi alacak bi iş var burda, bu yüzden vakit ayırmanız gerekiyor demedi demeyin... :)
**Kullandığım teknolojiler:**
- **Postfix** - SMTP sunucusu (mail gönderme/alma)
- **Dovecot** - IMAP/POP3 sunucusu (mail kutusuna erişim)
- **OpenDKIM** - DKIM imzalama (mail doğrulama)
- **SpamAssassin** - Spam filtreleme
- **Roundcube** - Web tabanlı mail istemcisi (Docker ile)
- **Nginx** - Reverse proxy ve SSL termination
- **Fail2ban** - Brute-force koruması
- **Let's Encrypt** - Ücretsiz SSL sertifikası
Sunucu ortamım: **Hetzner Cloud VPS**, **Ubuntu 24.04**, DNS yönetimi **Cloudflare** üzerinden. Bu rehber, temel Linux bilgisi olan herkese hitap ediyor...
> **NOT:** Daha önce MailCow denediğim için sunucumda bazı paketler (Postfix, Dovecot, Certbot vb.) zaten kuruluydu. Yazı boyunca bazı yerlerde "bende zaten kurulu" gibi ifadeler görebilirsiniz ama her paketin nasıl kurulacağını da açıklıyorum, merak etmeyin! :)
---
## Başlamadan Önce: Bu Terimler Ne Demek?
Rehbere başlamadan önce şunu söyleyeyim: bu alanda ilk defa karşılaşacağınız bir sürü kısaltma var ve hepsi başta göz korkutuyor - SMTP, IMAP, DKIM, SPF, PTR... Bu terimlerin hepsini zaten duyduysanız ve net anlamıyorsanız, aşağıdaki mini sözlük yanınızda bulunsun, ihtiyacınız olduğunda geri dönüp bakarsınız. Rehbere başlamak için ezberlemenize gerek yok - ama "bu neymiş ya?" dediğinizde buraya bir göz atmak size zaman kazandıracak:
- **SMTP (Simple Mail Transfer Protocol):** Mail **gönderim** protokolü. Sunucular arası mail alışverişi (port 25) ve kullanıcının mail sunucusuna mail bırakması (port 465/587) bu protokolle yapılır.
- **IMAP (Internet Message Access Protocol):** Mail **okuma/senkronizasyon** protokolü. Mesajlar sunucuda kalır, birden fazla cihazdan aynı kutuya bağlanabilirsin (port 143, 993).
- **POP3:** Eski mail okuma protokolü - mesaj sunucudan istemciye iner ve sunucudan silinir. Bugün genelde IMAP tercih edilir, POP3 legacy.
- **MTA (Mail Transfer Agent):** Mail trafiğini sunucular arası yöneten yazılım. Bu rehberde **Postfix**.
- **MDA (Mail Delivery Agent):** Mail'i son kullanıcının kutusuna yazan program. Bu rehberde **Dovecot** (LMTP/LDA) bu işi üstleniyor.
- **MUA (Mail User Agent):** Kullanıcının kullandığı arayüz - Gmail web, Outlook, Thunderbird vb. Bu rehberde webmail için **Roundcube**.
- **MX kaydı:** "Bu domain'e mail gönderilecek, hangi sunucuya gideyim?" sorusunun DNS cevabı. `orneksite.com` için MX kaydı `mail.orneksite.com` gibi bir host gösterir.
- **SPF (Sender Policy Framework):** "Bu domain adına hangi IP'ler mail göndermeye YETKİLİ?" listesini yayınlayan DNS TXT kaydı. Alıcı sunucular bunu okuyup "gerçekten yetkili misin?" diye sorgular.
- **DKIM (DomainKeys Identified Mail):** Gönderen sunucu mail'i bir private key ile kriptografik olarak imzalar. Alıcı sunucu DNS'ten public key'i okuyup imzayı doğrulayarak mail'in yolda değiştirilmediğinden emin olur. Hem DNS TXT kaydı hem sunucuda private key gerektirir.
- **DMARC (Domain-based Message Authentication, Reporting & Conformance):** SPF ve DKIM sonuçlarının nasıl değerlendirileceğini söyleyen politika. "SPF/DKIM fail olursa mail'i ne yapayım - kabul et, karantinaya al, yoksa reddet?" sorusunun cevabı.
- **PTR / Reverse DNS:** "Bu IP kime ait?" sorusunun cevabı. Mail sunucuları için IP'nin `mail.orneksite.com`'a çözülmesi beklenir - bu kayıt yoksa Gmail/Outlook gibi büyük sağlayıcılar maili kabul bile etmez.
- **Let's Encrypt / Certbot:** Ücretsiz SSL/TLS sertifikası dağıtan otorite (Let's Encrypt) ve onu otomatik olarak almanı sağlayan araç (certbot). Sertifika mail sunucunun TLS bağlantıları için ve webmail için HTTPS sağlamak için kullanılacak.
- **Milter:** "Mail filter" kısaltması. Postfix gibi MTA'ların mail gönderim/alım yolunda dışarıdan devreye giren filtreler. OpenDKIM bu yolla Postfix'e bağlanıp gelen/giden maillere DKIM imzası ekler.
- **Forward:** Bir adrese gelen maili otomatik olarak başka bir adrese (yerel veya harici) ileten kural. Postfix'te `virtual_alias_maps` üzerinden tanımlanır. cPanel'in "Forwarders" sekmesinin sunucu seviyesindeki karşılığı (sanırım).
- **SRS (Sender Rewriting Scheme):** Forward edilen mail'in envelope sender'ını rewrite eden mekanizma. SPF/DMARC kontrollerinin forward sırasında kırılmasını önler. Ubuntu'da `postsrsd` paketiyle gelir.
Bu kadarı şimdilik yeter. Rehber boyunca gerektiğinde daha detaylı değineceğim.
## Ön Gereksinimler
Başlamadan önce aşağıdakilere ihtiyacınız var:
**Sunucu:**
- Sabit (statik) IP adresine sahip bir VPS. Ben (son 2-3 yıldan uzun bir süredir) Hetzner Cloud kullanıyorum ama sizin varsa Turhost, DigitalOcean, Linode veya benzeri herhangi bir sağlayıcı o da olur. Önemli olan sabit IP ve port 25'in açılabilir olması.
- **Ubuntu 24.04 LTS** öneriyorum. Bu rehberdeki tüm komutlar Ubuntu/Debian tabanlı sistemler için.
- En az 2 GB RAM, 20 GB disk alanı yeterli.
**Domain ve DNS:**
- Bir domain adınız olmalı (örneğin `orneksite.com`). Birden fazla domain için de bu rehber geçerli. (Güvenlik gerekcesiyle gerçek bilgilerime yer vermedim!)
- DNS yönetimi için **Cloudflare** kullanmanızı şiddetle öneriyorum. Ücretsiz planı yeterli ve yönetimi çok kolay. (Zaten adamlar tekel, bu yüzdek pek bir alternatifimiz de yok...)
**Bilgi ve Erişim:**
- Temel Linux komut satırı bilgisi (dosya düzenleme, servis yönetimi, cli vb)
- SSH erişimi (root veya sudo yetkili kullanıcı)
- Bi de çokca sabır tabii ki de :) Ciddi anlamda sabır. Yani ilk seferinde her şey düzgün çalışmayabilir ve bu tamamen normal bir süreç... (Bu yüzden tüm aldığım hataları aktarmaya olabildiğince detaylandırmaya çalıştım bu yazıyı...)

> **Cloud-init tuzağı (Hetzner Cloud ve benzerleri):** Bu tamamen sinsi bir tuzak, çünkü her şey çalışıyor gibi gözüküyor - ta ki ilk reboot'a kadar. Hetzner Cloud gibi sağlayıcılarda sunucu cloud-init ile provision ediliyor ve `/etc/cloud/cloud.cfg` içinde `manage_etc_hosts: True` ayarı varsa, sonradan `/etc/hosts`'a elinle eklediğin `127.0.1.1 mail.orneksite.com mail` satırı reboot atınca **sessizce siliniyor**. Aynı dosyada `preserve_hostname: false` ise cloud-init, reboot sonrası hostname'i de orijinaline döndürüyor. Benim ilk kurulumumda reboot atınca mail servisleri kendi kendini bulmaz oldu, saatlerce **"ben yine neyi yanlış ettim"** diye sebepsizce bi sebep aradım durdum... **ÇÖZÜM:** ya `"manage_etc_hosts: false"` ve `"preserve_hostname: true"` yap ya da `"/etc/cloud/templates/hosts.debian.tmpl"` dosyasını düzenleyip mail hostname'ini oraya kalıcı olarak yaz.
> **NOT:** Bu rehberde (birden fazla domainle nasıl ayarlanırı gösterebilmek için) iki domain kullanıyorum: `orneksite.com` (birincil) ve `ikincidomain.com` (ikincil). Siz kendi domain adınızı kullanacaksınız. Komutlarda ve config dosyalarında gördüğünüz her yerde kendi domain adınızı yazın.
## Gerekli Paketlerin Kurulumu
Ben paket yöneticisi olarak **[nala](https://gitlab.com/volian/nala)**'yı kullanıyorum. Nala, APT'nin üzerine inşa edilmiş daha modern ve okunabilir bir arayüz. Kurulumları daha temiz gösteriyor, paralel indirme yapıyor ve işlem geçmişi tutuyor. Ama endişelenmeyin - eğer siz klasik APT kullanıyorsanız, komutlardaki `nala` yerine `apt` yazmanız yeterli. Birebir aynı şekilde çalışır.
Nala kurulumu (isteğe bağlı)
```bash
sudo apt install -y nala
```
Kurulduktan sonra en hızlı mirror'ları seçmek için:
```bash
sudo nala fetch
```
Önce sunucumuzu güncelleyelim:
```bash
sudo nala update && sudo nala upgrade -y
```
> **NOT:** Rehber boyunca config dosyalarını düzenlemek için `sudo vim ...` komutunu kullanıyorum (alışkanlık). Vim Ubuntu 24.04'te tipik olarak `vim-tiny` ile gelir ama tam sürüm için `sudo nala install -y vim` çalıştırın. Vim'e aşina değilseniz `sudo nala install -y nano` ile nano'yu kurup komutlardaki `vim`'i `nano` ile değiştirebilirsiniz, davranış aynıdır; sadece editör tercihi.
Ardından ihtiyacımız olan her paketi tek tek kuralım. Her birinin ne işe yaradığını ve nasıl doğrulanacağını açıklıyorum.
Postfix Kurulumu
Postfix, mail gönderme ve alma işini yapan SMTP sunucusu. Mail dünyasının kalbi diyebiliriz.
```bash
sudo nala install postfix -y
```
Kurulum sırasında bir yapılandırma ekranı çıkacakrsa, **"Internet Site"** seçeneğini seçin ve "System mail name" olarak ana domain adınızı girin (örneğin `orneksite.com`).
> **İpucu (noninteractive kurulum):** Postfix `apt install` komutu varsayılan olarak iki sayfalık mor renkli bir TUI ekranı açıyor (mail tipi seçimi + mailname). Eğer otomasyon script'i yazıyorsanız veya bir SSH oturumunuzun bu interaktif ekranda kilitlenmesini istemiyorsanız (ki benim başıma geldi, `tmux` içinde çalışırken ekran donmuş sandım), kurulumdan önce cevapları `debconf`'a preseed edin:
>
> ```bash
> echo "postfix postfix/mailname string orneksite.com" | debconf-set-selections
> echo "postfix postfix/main_mailer_type string 'Internet Site'" | debconf-set-selections
> DEBIAN_FRONTEND=noninteractive apt install -y postfix postfix-pcre
> ```
>
> Bu şekilde kurulum hiçbir şey sormadan, arka planda tamamlanıyor. `postfix-pcre` paketini de aynı anda almayı unutmayın, `login_maps.pcre` dosyası için ilerde zaten lazım olacak.
Doğrulama:
```bash
postconf mail_version
# postfix: 3.x.x şeklinde bir çıktı görmeli
systemctl status postfix
```
Dovecot Kurulumu
Dovecot, mail kutunuza IMAP ve POP3 protokolleri üzerinden erişmenizi sağlar. Roundcube gibi mail istemcileri, maillerinizi okumak için Dovecot'a bağlanır.
```bash
sudo nala install dovecot-imapd dovecot-pop3d dovecot-sieve dovecot-lmtpd -y
```
- `dovecot-imapd` - IMAP desteği
- `dovecot-pop3d` - POP3 desteği
- `dovecot-sieve` - Mail filtreleme kuralları (örneğin spam'i otomatik Junk'a taşıma)
- `dovecot-lmtpd` - Yerel mail teslimatı
Doğrulama:
```bash
dovecot --version
systemctl status dovecot
```
OpenDKIM Kurulumu
OpenDKIM, gönderdiğiniz maillere dijital imza ekler. Bu imza sayesinde alıcı sunucu, mailin gerçekten sizden geldiğini doğrulayabilir. SPF ve DMARC ile birlikte "mail güvenilirlik üçlüsü"nü oluşturur.
```bash
sudo nala install opendkim opendkim-tools -y
```
Postfix kullanıcısını `opendkim` grubuna ekleyin:
```bash
sudo usermod -aG opendkim postfix
```
Doğrulama:
```bash
opendkim -V
# veya
nala show opendkim
```
SpamAssassin Kurulumu
SpamAssassin, gelen mailleri spam puanlaması yaparak filtreler.
```bash
sudo nala install spamassassin spamc -y
sudo systemctl enable spamd
sudo systemctl start spamd
```
> **Dikkat (Ubuntu 24.04):** SpamAssassin'in servis adı `spamassassin` değil, **`spamd`** olarak değişti. `systemctl enable spamassassin` derseniz `Unit not found` hatası alırsınız. Tüm `systemctl` komutlarında `spamd` kullanın.
Doğrulama:
```bash
systemctl status spamd
```
Certbot / Let's Encrypt Kurulumu
SSL sertifikası olmadan mail sunucusu çalıştırmak hem güvensiz hem de diğer sunucuların (Gmail vb.) sizi reddetmesine, spamlamasına neden olur.
```bash
sudo nala install certbot -y
```
Sertifika almak için (Nginx henüz kurulu değilse standalone modda):
```bash
sudo certbot certonly --standalone -d mail.orneksite.com
```
Eğer Nginx zaten çalışıyorsa:
```bash
sudo certbot certonly --nginx -d mail.orneksite.com
```
Sertifikalar `/etc/letsencrypt/live/mail.orneksite.com/` altında oluşacak.
Nginx Kurulumu
Nginx, Roundcube'un önünde reverse proxy olarak çalışacak ve SSL termination yapacak.
```bash
sudo nala install nginx -y
sudo systemctl enable nginx
```
Docker ve Docker Compose Kurulumu
Roundcube'u Docker ile çalıştıracağız. Bu sayede web uygulaması ve veritabanı ana sistemden izole olur.
```bash
# Docker'ın resmi GPG anahtarını ekle
sudo nala install ca-certificates curl gnupg -y
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Docker repo'sunu ekle
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Kur
sudo nala update
sudo nala install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
```
Doğrulama:
```bash
docker --version
docker compose version
```
Fail2ban Kurulumu
Fail2ban, brute-force saldırılarını tespit edip saldırganın IP'sini otomatik olarak banlayan bir güvenlik aracı.
```bash
sudo nala install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
```
UFW (Firewall) Kurulumu
UFW, Ubuntu'nun basit firewall yönetim aracı.
```bash
sudo nala install ufw -y
```
Temel kurallar (detaylı yapılandırmayı ileride yapacağız):
```bash
sudo ufw allow ssh
sudo ufw enable
```
> **Uyarı:** SSH'yi allow etmeden `ufw enable` yaparsanız, sunucuya erişiminizi kaybedersiniz!
## Mimari Genel Bakış
Kuruluma geçmeden önce, büyük resmi görelim. Tüm bileşenlerin birbirleriyle nasıl konuştuğunu anlamak, ilerideki hata ayıklamada hayat kurtarır.
```mermaid
flowchart LR
DNS["Cloudflare DNS
(DNS only)"]
subgraph SUNUCU["SUNUCU"]
direction TB
Postfix["Postfix
:25 / :587 / :465"]
Dovecot["Dovecot
:993 / :143"]
Roundcube["Roundcube
:8080"]
OpenDKIM["OpenDKIM
:12301"]
SpamAssassin["SpamAssassin"]
PostgreSQL["PostgreSQL
(Docker)"]
Nginx["Nginx
:443"]
Fail2ban["Fail2ban"]
Postfix --> Dovecot
Roundcube --> Dovecot
Postfix --> OpenDKIM
Postfix -. milter .-> SpamAssassin
Roundcube --> PostgreSQL
end
DNS -->|MX/25| Postfix
DNS -->|SMTP/587| Postfix
DNS -->|HTTPS/443| Nginx
```
**Port haritası:**
| Port | Protokol | Açıklama |
|------|------------|---------------------------------|
| 25 | SMTP | Sunucular arası mail alışverişi |
| 143 | IMAP | Mail okuma (STARTTLS ile) |
| 443 | HTTPS | Roundcube web arayüzü (Nginx) |
| 465 | SMTPS | Mail gönderme (SSL/TLS sarmalı) |
| 587 | Submission | Mail gönderme (STARTTLS) |
| 993 | IMAPS | Mail okuma (SSL/TLS sarmalı) |
**Neden bu mimari?**
**Roundcube** ve **PostgreSQL**'i Docker ile izole ettim. Böylece web uygulamasındaki bir güvenlik açığı doğrudan sunucuyu etkilemez. **Postfix** ve **Dovecot** ise host üzerinde çalışıyor çünkü doğrudan port erişimi ve dosya sistemi entegrasyonu gerekiyor. Bu hibrit yaklaşım, güvenlik ile yönetilebilirlik arasında iyi bir denge sağlıyor.
## Adım 1: Yedekleme
Eğer mevcut bir sunucu üzerinde çalışıyorsanız, herhangi bir değişiklik yapmadan önce mutlaka yedek alın. "Bana bir şey olmaz" diyenler için: bana oldu :(
```bash
sudo cp -r /etc/postfix /etc/postfix.backup.$(date +%Y%m%d)
sudo cp -r /etc/dovecot /etc/dovecot.backup.$(date +%Y%m%d)
sudo cp -r /etc/opendkim.conf /etc/opendkim.conf.backup.$(date +%Y%m%d)
sudo cp -r /etc/nginx /etc/nginx.backup.$(date +%Y%m%d)
sudo cp -r /etc/fail2ban /etc/fail2ban.backup.$(date +%Y%m%d)
```
> **NOT:** Temiz bir kurulum yapıyorsanız (yeni sunucu için) bu adımı atlayabilirsiniz. Ama yine de alışkanlık edinmenizi öneririm!
## Adım 2: Sistem Kullanıcıları
Mail sunucusunda her mail adresi bir sistem kullanıcısına karşılık gelir. Bu kullanıcıların shell erişimi olmamalı - sadece mail alıp gönderebilmeliler.
```bash
# orneksite.com domain'i için kullanıcı
sudo useradd -m -s /usr/sbin/nologin -G mail kullanici1
# ikincidomain.com domain'i için kullanıcı
sudo useradd -m -s /usr/sbin/nologin -G mail kullanici2
```
Şifre belirleyin:
```bash
sudo passwd kullanici1
sudo passwd kullanici2
```
Mail dizin yapısını oluşturun (Maildir formatı):
```bash
# kullanici1
sudo mkdir -p /home/kullanici1/Mail/Inbox
sudo mkdir -p /home/kullanici1/Mail/Sent
sudo mkdir -p /home/kullanici1/Mail/Drafts
sudo mkdir -p /home/kullanici1/Mail/Trash
sudo mkdir -p /home/kullanici1/Mail/Junk
sudo chown -R kullanici1:kullanici1 /home/kullanici1/Mail
# kullanici2
//...
```
### PAM'dan passwd-file'a Geçiş: İlk Büyük Karar
İlk başta Dovecot'un varsayılan PAM kimlik doğrulamasını kullanmayı planlamıştım. Yani kullanıcılar Linux kullanıcı adlarıyla (kullanici1, kullanici2) giriş yapacaklardı. Ama bir sorun vardı: ben `ben@orneksite.com` gibi özel mail adresleri kullanmak istiyordum. Linux kullanıcı adı `kullanici1` ama mail adresi `ben@orneksite.com` - PAM bunu doğrulayamazdı.
**ÇÖZÜM:** Dovecot'un `passwd-file` kimlik doğrulama yöntemine geçmek. Bu sayede mail adresi ile kullanıcı eşlemesini kendim tanımlayabildim. Detaylarını **Dovecot** yapılandırması bölümünde anlatacağım!
## Adım 3: Cloudflare DNS Yapılandırması
**DNS'i neden baştan yapıyoruz?** Mantıksız gibi gelebilir - "daha Postfix'i bile kurmadan niye DNS ile uğraşıyoruz?" Cevabı basit: bu rehberde ilerleyen adımlardaki SSL sertifikası (Let's Encrypt, Adım 9'da Nginx bölümünde kullanacağız), `mail.orneksite.com` için doğrulama yapabilmek için `A` kaydının yayında olmasını gerektiriyor. Ayrıca DNS yapılandırılması bazen 5 dakika, bazen birkaç saat sürüyor - önceden başlatıp arkaaplanda olgunlaşmasına izin vermek, kurulumun sonunda "hâlâ yapılandırma/propagasyon olmadı" diye beklemekten çok daha akıllıca. Ben (yıllar yıllar önce) ilk kurulumumda bu sırayı bilmediğim için SSL aşamasında tıkanmıştım, ikinci kurulumda DNS'i en başa aldım ve Postfix/Dovecot kurarken arka planda DNS'in yapılandırması bitmiş oldu...
İşte tam bu noktada, en kritik adımlardan birine geldik. **DNS kayıtları doğru yapılandırılmazsa, mailleriniz hiçbir yere ulaşamaz!** Ve burada bir kural var ki, bunu öğrenmem saatlerimi aldı...
> **KRİTİK KURAL:** Mail ile ilgili TÜM DNS kayıtları **"DNS only"** (gri bulut) modunda olmalıdır! Cloudflare proxy'si (turuncu bulut) SMTP trafiğini desteklemez. Bu kuralı unutursanız, saatlerce neden mail gönderemediğinizi düşünüp, debelenip durursunuz!
### orneksite.com Domain'i
Bu domain için gereken DNS kayıtları:
| Tür | Ad | İçerik | Proxy |
|------|-----|--------|-------|
| A | mail | `SUNUCU_IP_ADRESINIZ` | DNS only (gri bulut) |
| AAAA | mail | `SUNUCU_IPV6_ADRESINIZ` | DNS only (gri bulut) |
| MX | orneksite.com | `mail.orneksite.com` (öncelik: 10) | - |
| TXT | orneksite.com | `v=spf1 mx a:mail.orneksite.com ip4:SUNUCU_IP ip6:SUNUCU_IPV6 -all` | - |
| TXT | _dmarc | `v=DMARC1; p=quarantine; rua=mailto:postmaster@orneksite.com; fo=1` | - |
> **NOT:**
>
> SPF kaydında `ip4` ve `ip6` ile sunucu IP'nizi açıkça belirtmenizi öneririm. Sadece `mx` veya `a:` kullanmak bazı durumlarda yeterli olmayabiliyor - özellikle birden fazla domain barındırıyorsanız. AAAA kaydını da eklemeyi unutmayın; IPv6 üzerinden gelen bağlantılar da TLS ile düzgün çalışsın.
>
> DKIM TXT kaydı bu aşamada henüz üretilmedi! Tabloda dikkat ederseniz `mail._domainkey` satırı görmüyorsunuz. DKIM public key'i Adım 6'da (OpenDKIM kurulumu) üreteceğiz, oraya geldiğinde Cloudflare panelini tekrar açıp `mail._domainkey` TXT kaydını ekleyeceğiz. Şimdilik DKIM kaydını bu adıma yazmaya kalkarsan elinde key olmadığı için boş bir şey ekleyeceksin - hiçbir işe yaramaz. Oraya geldiğinde seni uyaracağım.
### ikincidomain.com Domain'i (Sıfırdan Ekleme)
İkinci bir domain eklemek istediğinizde, aynı yapıyı tekrarlarsınız. Tek fark: SPF'e ana mail sunucunuzun A kaydını da eklemeniz iyi olur (böylece her iki domain de aynı IP'den gönderim yapabilir):
| Tür | Ad | İçerik | Proxy |
|------|-----|--------|-------|
| A | mail | `SUNUCU_IP_ADRESINIZ` | DNS only (gri bulut) |
| AAAA | mail | `SUNUCU_IPV6_ADRESINIZ` | DNS only (gri bulut) |
| MX | ikincidomain.com | `mail.ikincidomain.com` (öncelik: 10) | - |
| TXT | ikincidomain.com | `v=spf1 mx a:mail.ikincidomain.com a:mail.orneksite.com ip4:SUNUCU_IP ip6:SUNUCU_IPV6 -all` | - |
| TXT | _dmarc | `v=DMARC1; p=quarantine; rua=mailto:postmaster@ikincidomain.com; fo=1` | - |
### DNS Kayıt Detayları
**SPF (Sender Policy Framework):** Hangi sunucuların sizin domain adınız adına mail gönderebileceğini belirler. `v=spf1 mx a:mail.orneksite.com -all` ifadesi şunu der: "Sadece MX kaydındaki ve mail.orneksite.com'deki sunucu benim adıma mail gönderebilir, diğerlerini reddet."
**DMARC:** `p=quarantine` ile başlayın. Bu, DKIM/SPF doğrulaması başarısız olan maillerin spam'e atılmasını söyler. Her şey stabil çalışmaya başladıktan sonra `p=reject`'e yükseltebilirsiniz. (Güvenlik sıkılaştırma bölümünde DMARC'ın kademeli rollout stratejisini detaylı anlattım, oraya da göz at.)
### DNS Doğrulama
```bash
# Kur
nala install -y bind9-dnsutils
# Hata verirse veya terminal etkileşimi takılırsa bunu dene
DEBIAN_FRONTEND=noninteractive nala install -y bind9-dnsutils
# Zaten kuruluysa, dig binary'sinin PATH'te olduğunu doğrula
which dig 2>&1; dpkg -L bind9-dnsutils 2>&1 | grep bin/dig
# Path sorun çııkarırsa: Bu komut shell'in komut önbelleğini temizler. Sonra dig çalışacaktır!
hash -r
```
```bash
# MX kaydını doğrula
dig MX orneksite.com +short
# Beklenen: 10 mail.orneksite.com.
# SPF kaydını doğrula
dig TXT orneksite.com +short
# Beklenen: "v=spf1 mx a:mail.orneksite.com -all"
# DMARC kaydını doğrula
dig TXT _dmarc.orneksite.com +short
# Beklenen: "v=DMARC1; p=quarantine; ..."
```
*DKIM kaydını (`dig TXT mail._domainkey.orneksite.com`) şu anda sorgularsan sonuç boş olacak - çünkü henüz eklemedik. Adım 6'da üreteceğiz.*
> **NOT:** DNS değişiklikleri propagasyon nedeniyle birkaç dakika ile birkaç saat arasında sürebilir. Sabırlı olun, yapılandırma arkaplanda olgunlaşırken siz sonraki adımlara geçin.

## Adım 4: Hetzner PTR (Reverse DNS) Kaydı
PTR kaydı, IP adresinizden domain adınıza geri dönük bir DNS kaydıdır. Birçok büyük mail sağlayıcı (Gmail, Outlook, Yahoo), gelen maillerin PTR kaydını kontrol eder. PTR kaydı yoksa veya yanlışsa, mailleriniz büyük olasılıkla spam'a düşer ya da doğrudan reddedilir!
PTR'yi de Cloudflare DNS gibi baştan halletmek mantıklı: işletim sistemi kurulumu ile paralelde Hetzner paneline girip iki dakikada hallediliyor, sonraki adımlarda "hep geri dönüp bir şeyi eklemek zorunda kaldım" diye bi derdimiz olmuyor... :)
### IPv4 PTR Kaydı
1. Hetzner Cloud Panel'e giriş yapın
2. Sunucunuzu seçin
3. **Networking** sekmesine gidin
4. IP adresinizin yanındaki **"..."** menüsüne tıklayın
5. **"Edit Reverse DNS"** seçin
6. `mail.orneksite.com` yazın ve kaydedin
### IPv6 PTR Kaydı
Aynı işlemi IPv6 adresiniz için de yapın. Hetzner her sunucuya bir IPv6 /64 bloğu verir. Birincil IPv6 adresi için PTR kaydını yine `mail.orneksite.com` olarak ayarlayın.

### Doğrulama
```bash
# IPv4 PTR
dig -x SUNUCU_IP_ADRESINIZ +short
# Beklenen: mail.orneksite.com.
# Veya host komutuyla
host SUNUCU_IP_ADRESINIZ
# Beklenen: 251.219.69.159.in-addr.arpa domain name pointer mail.orneksite.com.
```
## Adım 5: Postfix Yapılandırması
İşte tam bu noktada, işler ciddiyet kazanmaya başlıyor... Postfix, mail sunucumuzun kalbi ve en fazla yapılandırma gerektiren bileşen.
### main.cf - Ana Yapılandırma
`/etc/postfix/main.cf` dosyasını düzenleyin:
> **UYARI:** Aşağıdaki config'de `/etc/letsencrypt/live/mail.orneksite.com/` altındaki sertifika yollarını göreceksin ama şu anda o dosyalar henüz yok - sertifikayı Adım 9'da (Nginx Reverse Proxy) Let's Encrypt ile alacağız. Postfix servisini de Adım 10'a kadar başlatmıyoruz, yani o zamana kadar sertifika hazır olmuş olacak. Şimdi sadece yollarını config'e yazıyoruz, şimdilik endişelenme.
```ini
# Temel ayarlar
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 3.6
# TLS parametreleri
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.orneksite.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.orneksite.com/privkey.pem
smtpd_tls_security_level = may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_CAfile = /etc/letsencrypt/live/mail.orneksite.com/cert.pem
# Kimlik doğrulama olmadan TLS zorunlu (şifreler şifrelenmeden ağa çıkmasın)
smtpd_tls_auth_only = yes
# Eski, güvensiz protokolleri devre dışı bırak
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# SASL kimlik doğrulama (Dovecot üzerinden)
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
# Relay kısıtlamaları
smtpd_relay_restrictions = permit_sasl_authenticated, reject_unauth_destination
# Domain ve ağ ayarları
myhostname = mail.orneksite.com
mydomain = orneksite.com
myorigin = /etc/mailname
mydestination = $myhostname, $mydomain, ikincidomain.com, mail, localhost.localdomain, localhost, localhost.$mydomain
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.16.0.0/12
mail_name = orneksite.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
# Genel
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
home_mailbox = Mail/Inbox/
# Gönderici kısıtlamaları (sahte adres göndermeyi engelle)
smtpd_sender_login_maps = pcre:/etc/postfix/login_maps.pcre
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_sender_login_mismatch, reject_unknown_reverse_client_hostname, reject_unknown_sender_domain
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unknown_recipient_domain
# HELO kısıtlamaları
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
# Başlık gizleme (gizlilik için)
header_checks = regexp:/etc/postfix/header_checks
# DKIM (OpenDKIM milter)
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301
# Dovecot LDA ile teslimat
mailbox_command = /usr/lib/dovecot/deliver
# SMTP smuggling koruması
smtpd_forbid_bare_newline = normalize
smtpd_forbid_bare_newline_exclusions = $mynetworks
# Sanal alias eşlemeleri
virtual_alias_maps = hash:/etc/postfix/virtual
```
**Birkaç kritik noktayı açıklayayım:**
- **mynetworks**: `172.16.0.0/12` Docker bridge ağını içerir. Roundcube Docker'dan Postfix'e bağlanabilsin diye gerekli.
- **smtpd_tls_auth_only = yes**: Şifreler yalnızca TLS bağlantıları üzerinden kabul edilir. Bu olmadan şifreler açık metin olarak ağda dolaşır.
- **smtpd_forbid_bare_newline**: SMTP smuggling saldırılarına karşı koruma. Postfix 3.8+ ile gelen yeni bir güvenlik özelliği.
**Config'in kritik satırlarını özetlemem gerekirse:**
- `smtpd_sasl_type = dovecot` (kim doğrulama yapıyor: Dovecot)
- `smtpd_milters = inet:localhost:12301` (DKIM imzalayan: OpenDKIM)
- `mynetworks` (güvenilir iç ağ tanımı)
- `virtual_alias_maps` (mail adresini Linux kullanıcısına çeviren dosya)
- ... gerisi TLS ve kısıtlama detayları.
### master.cf - Servis Tanımları
`/etc/postfix/master.cf` dosyasının sonuna (veya mevcut smtp/submission tanımlarını değiştirerek) şunları ekleyin:
```
# SpamAssassin filtresi
spamassassin unix - n n - - pipe
user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
# SMTP - gelen maillar SpamAssassin'den geçer
smtp inet n - y - - smtpd
-o content_filter=spamassassin
# Submission (587) - kimlik doğrulamalı mail gönderme
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_auth_only=yes
-o smtpd_enforce_tls=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_sender_login_maps=pcre:/etc/postfix/login_maps.pcre
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination
# SMTPS (465) - SSL sarmalı mail gönderme
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
```
### login_maps.pcre - Gönderici Kısıtlamaları
Bu dosya, hangi kullanıcının hangi adresten mail gönderebileceğini tanımlar. Bir kullanıcının başka birinin adresinden mail atmasını engeller.
`/etc/postfix/login_maps.pcre`:
```
/^ben@orneksite\.com$/ ben@orneksite.com
/^info@ikincidomain\.com$/ info@ikincidomain.com
```
### header_checks - Başlık Gizleme
Gönderdiğiniz maillerdeki bazı dahili bilgileri gizleyebilirsiniz. Ama burada çok kritik bir tuzak var...
> **UYARI:** RECEIVED HEADER'LARI SİLMEYİN!
>
> Ben ilk kurulumda gizlilik takıntısıyla `Received` başlıklarını da silmiştim. Sonuç? **Tüm maillerim spam'a düştü.** Saatlerce nedenini aradım - SPF doğru, DKIM doğru, DMARC doğru, IP temiz... Sonunda SpamAssassin loglarında `NO_RECEIVED, NO_RELAYS` bayraklarını gördüm. Meğerse `Received` başlıkları mailin yönlendirme zincirini gösterir ve bu başlıkların olmaması, gönderenin izini gizlemeye çalıştığı anlamına gelir. Gmail, Outlook ve diğer büyük sağlayıcılar bunu **yüksek spam skoru** ile cezalandırır. Yani gizlilik için yaptığınız şey, tam tersi etki yapıyor!
>
> **Sadece `X-Originating-IP` satırını kullanın.** `Received` satırını **kesinlikle eklemeyin.**
`/etc/postfix/header_checks`:
```
/^X-Originating-IP:/ IGNORE
```
`X-Originating-IP` başlığı istemci IP'nizi gösterir - bunu gizlemek makul. Ama `Received` başlıkları mail altyapısının temel parçası, onlara dokunmayın!
### virtual - Sanal Alias Eşlemeleri
Özel mail adreslerini sistem kullanıcılarıyla eşler.
`/etc/postfix/virtual`:
```
ben@orneksite.com kullanici1
info@ikincidomain.com kullanici2
```
Hash dosyasını oluşturun:
```bash
sudo postmap /etc/postfix/virtual
```
## Adım 6: OpenDKIM - DKIM Anahtar Üretimi
DKIM (DomainKeys Identified Mail), gönderdiğiniz her maile kriptografik bir imza ekler. Alıcı sunucu, DNS'teki public key ile bu imzayı doğrulayarak mailin gerçekten sizin sunucunuzdan geldiğini teyit eder. SPF ve DMARC ile birlikte, maillerinizin spam klasörüne düşmemesinin en önemli garantisi.
### Anahtar Üretimi
Her domain için ayrı DKIM anahtarı üretmeniz gerekiyor:
```bash
# orneksite.com için
sudo mkdir -p /etc/postfix/dkim/orneksite.com
sudo opendkim-genkey -b 2048 -d orneksite.com -D /etc/postfix/dkim/orneksite.com -s mail -v
sudo chown opendkim:opendkim /etc/postfix/dkim/orneksite.com/mail.private
# ikincidomain.com için
sudo mkdir -p /etc/postfix/dkim/ikincidomain.com
sudo opendkim-genkey -b 2048 -d ikincidomain.com -D /etc/postfix/dkim/ikincidomain.com -s mail -v
sudo chown opendkim:opendkim /etc/postfix/dkim/ikincidomain.com/mail.private
```
Her domain'in `mail.txt` dosyasında DNS'e eklenecek public key yer alır.
> **Şimdi Cloudflare paneline geri dön, `mail._domainkey` TXT kaydını ekle:** Adım 3'te DNS kayıtlarını yaparken DKIM satırını **kasıtlı olarak** atlamıştık çünkü o noktada henüz key üretilmemişti. İşte şimdi key elimizde - Cloudflare'i açıp her domain için şu TXT kaydını eklememiz gerekiyor:
>
> | Tür | Ad | İçerik | Proxy |
> |------|-----|--------|-------|
> | TXT | `mail._domainkey` | `v=DKIM1; h=sha256; k=rsa; p=MIIBIjANB...` (public key) | - |
>
> İçerik alanına yukarıdaki `mail.txt` dosyasının tek satır haline getirilmiş hali gelecek (one-liner komutunu az önce verdim). Her domain (`orneksite.com`, `ikincidomain.com`) için ayrı DKIM TXT'si eklemeyi unutma!
> **Pratik ipucu - Cloudflare'e tek satırda yapıştırma:** `mail.txt` dosyasını `cat` ile açtığınızda DKIM public key `"v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANB..." "..."` şeklinde çok satırlı ve tırnaklı olarak geliyor. Cloudflare'in TXT kayıt alanı tek satır istiyor - ben ilk seferinde tırnakları elle temizleyip satırları birleştirmeye uğraştım ve bir karakter kaçırınca DKIM doğrulamasının saatlerce fail etmesine sebep oldum. Şu komut ile zahmetsizce tek satıra çeviriyorum:
>
> ```bash
> grep -oE '"[^"]*"' /etc/postfix/dkim/orneksite.com/mail.txt | tr -d '"' | tr -d '\n'
> ```
>
> Bu komutun çıktısını direkt Cloudflare'deki TXT içerik alanına yapıştırıyorum, hiçbir manuel düzenleme gerekmiyor.

### OpenDKIM Yapılandırması
`/etc/opendkim.conf`:
```ini
Syslog yes
SyslogSuccess yes
Canonicalization relaxed/simple
Mode sv
OversignHeaders From
UserID opendkim
UMask 007
PidFile /run/opendkim/opendkim.pid
TrustAnchorFile /usr/share/dns/root.key
KeyTable file:/etc/postfix/dkim/keytable
SigningTable refile:/etc/postfix/dkim/signingtable
InternalHosts refile:/etc/postfix/dkim/trustedhosts
Socket inet:12301@localhost
```
> **`/etc/default/opendkim`**, eski dosyayla vakit kaybetmeyin! Ben ilk kurulumda refleks olarak bu dosyayı açtım ve içinde `SOCKET=local:$RUNDIR/...` tanımını görünce "tamam, burdan değiştirmem lazım" diye düzenledim, servisi restart ettim, çalışmadı. Tekrar açıp değiştirdim, tekrar çalışmadı. Yarım saat sonra dosyanın başında sessiz sedasız yazılmış **"This is a legacy configuration file. It is not used by the opendkim systemd service"** uyarısını fark ettim. Ubuntu 24.04'teki systemd unit bu dosyayı tamamen yok sayıyor - gerçek socket ayarı yukarıdaki "`/etc/opendkim.conf`"'taki "`Socket inet:12301@localhost`" satırı. Eski dosyayı hiç açmayın bile, tüm işlem "`opendkim.conf`" üzerinden.
### KeyTable
`/etc/postfix/dkim/keytable`:
```
mail._domainkey.orneksite.com orneksite.com:mail:/etc/postfix/dkim/orneksite.com/mail.private
mail._domainkey.ikincidomain.com ikincidomain.com:mail:/etc/postfix/dkim/ikincidomain.com/mail.private
```
### SigningTable
`/etc/postfix/dkim/signingtable`:
```
*@orneksite.com mail._domainkey.orneksite.com
*@ikincidomain.com mail._domainkey.ikincidomain.com
```
### TrustedHosts
`/etc/postfix/dkim/trustedhosts`:
```
127.0.0.1
10.1.0.0/16
172.16.0.0/12
```
> **Önemli:** `172.16.0.0/12` Docker subnet'ini trustedhosts'a eklemeyi unutmayın. Roundcube Docker container'ından gönderilen mailler de DKIM ile imzalanmalı.
> **Doğru sahiplik - `opendkim:opendkim 600` vs `root:opendkim 640` karmaşası:** Rehberde farklı yerlerde iki farklı izin ipucu geçecek ve bu kafa karıştırabilir. Gerçeği şu: `opendkim:opendkim 600` hem çalışır hem güvenli - `opendkim` kullanıcısı kendi anahtarını okur, Postfix milter üzerinden (inet:12301) alır, private key hiç postfix kullanıcısının eline geçmez. `root:opendkim 640` seçeneği de çalışır ve `postfix check` çıktısındaki "not owned by root" uyarısını susturur, ama anahtarı group-readable yapar (opendkim grubundaki her kullanıcı okuyabilir hale gelir). Ben `opendkim:opendkim 600`'de duruyorum; `postfix check` uyarısı gürültüden başka bir şey yapmıyor, gerçek bir sorun işareti değil. İkisini de denemiştim - ikinci seçenek daha az "warning" verse de, daha sıkı izin olan ilkini tercih etmek bana daha mantıklı geldi.
Servisi yeniden başlatın:
```bash
sudo systemctl restart opendkim
sudo systemctl enable opendkim
```
## Adım 7: Dovecot Yapılandırması
Saatlerdir neden çalışmadığını anlayamıyordum, ta ki Dovecot yapılandırmasının ne kadar kritik olduğunu fark edene kadar. Dovecot, mail kutunuza erişimi yöneten bileşen ve her şeyin düzgün çalışması için en hassas yapılandırmayı gerektiriyor.
### Tam dovecot.conf
`/etc/dovecot/dovecot.conf` dosyasını **tamamen** aşağıdakiyle değiştirin:
```ini
# Dovecot config
# %u = kullanıcı adı (ben@orneksite.com)
# %n = @ öncesi kısım (ben)
# %d = domain (orneksite.com)
# %h = kullanıcının home dizini
# --- SSL / TLS bölümü ---
ssl = required
login_trusted_networks = 127.0.0.0/8 172.16.0.0/12
ssl_cert = **Uyarı: İzin Tuzağı!** Bu dosyayı her düzenlediğinizde izinleri kontrol edin! Ben bir keresinde dosyayı düzenledikten sonra izinler sessizce `640`'a düştü ve **tüm mail hesapları** "Oturum açılamadı" hatası vermeye başladı - sadece yeni eklenen değil, mevcut hesaplar da dahil. Dovecot auth servisi `dovecot` kullanıcısı (uid=113) olarak çalışır ve root grubunda değildir. `640` izinle dosyayı okuyamaz. Logda şunu görürsünüz:
>
> `passwd-file: open(/etc/dovecot/users) failed: Permission denied (euid=113(dovecot) egid=117(dovecot) missing +r perm)`
>
> **Kural:** Bu dosyayı her düzenlediğinizde `sudo chmod 644 /etc/dovecot/users` çalıştırın. Alışkanlık edinin!
### Sieve İzinleri
Sieve filtreleri için dizin ve dosya izinlerini düzeltin:
```bash
sudo mkdir -p /var/lib/dovecot/sieve
sudo chmod 755 /var/lib/dovecot/sieve
```
Servisi yeniden başlatın:
```bash
sudo systemctl restart dovecot
sudo systemctl enable dovecot
```
## Adım 8: Roundcube Docker Kurulumu
Artık mail gönderme ve alma altyapısı hazır. Şimdi kullanıcı dostu bir web arayüzü ekleyelim. Roundcube, PHP tabanlı, açık kaynak, modern bir webmail istemcisi - cPanel'den, Plesk'ten vs zaten çok aşina olduğum bir arayüz. **MailCow** gibi dev bir altyapı yerine, ihtiyacım olan tam olarak bu sadelikti.
Roundcube'u neden Docker ile kuruyorum? Sunucumdaki diğer bi çok web projesini de zaten Docker Compose ile izole şekilde çalıştırıyorum. Bu yaklaşıma aşinayım ve büyük avantajları var: olası bir sorunla karşılaştığımda `docker compose down` deyip her şeyi temiz bir şekilde geri alabilirim. Bir şey bozulursa ana sisteme dokunmadan sıfırlayabilirim. MailCow'la yaşadığım kötü deneyimden sonra, "geri alınabilirlik" benim için birincil öncelik haline gelmişti!
### Dizin Yapısı
```bash
sudo mkdir -p /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com/config
sudo mkdir -p /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com/logs
cd /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com
```
> **NOT:** Ben Hetzner'in ek depolama volume'ünü kullanıyorum (`/mnt/HC_Volume_XXXXXXXX`). Siz istediğiniz bir dizini kullanabilirsiniz, örneğin `/opt/roundcube` veya `/srv/mail`.
### docker-compose.yml
```yaml
networks:
roundcube-net:
driver: bridge
services:
app-roundcube-db:
image: postgres:16-alpine
container_name: db-postgresql-roundcube
restart: unless-stopped
volumes:
- ./db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: roundcube
POSTGRES_USER: roundcube
POSTGRES_PASSWORD: BURAYA_GÜÇLÜ_BİR_ŞİFRE_YAZIN
networks:
- roundcube-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U roundcube"]
interval: 10s
timeout: 5s
retries: 5
app-roundcube:
image: roundcube/roundcubemail:latest-apache
container_name: app-roundcube
restart: unless-stopped
depends_on:
app-roundcube-db:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
volumes:
- ./config:/var/roundcube/config
- ./logs:/var/roundcube/logs
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
ROUNDCUBEMAIL_DB_TYPE: pgsql
ROUNDCUBEMAIL_DB_HOST: app-roundcube-db
ROUNDCUBEMAIL_DB_PORT: "5432"
ROUNDCUBEMAIL_DB_NAME: roundcube
ROUNDCUBEMAIL_DB_USER: roundcube
ROUNDCUBEMAIL_DB_PASSWORD: BURAYA_GÜÇLÜ_BİR_ŞİFRE_YAZIN
ROUNDCUBEMAIL_DEFAULT_HOST: ssl://host.docker.internal
ROUNDCUBEMAIL_DEFAULT_PORT: "993"
ROUNDCUBEMAIL_SMTP_SERVER: ssl://host.docker.internal
ROUNDCUBEMAIL_SMTP_PORT: "465"
ROUNDCUBEMAIL_PLUGINS: archive,zipdownload,managesieve
ROUNDCUBEMAIL_SKIN: elastic
ROUNDCUBEMAIL_ASPELL_DICTS: tr
networks:
- roundcube-net
```
Birkaç önemli detay:
- **`127.0.0.1:8080:80`**: Roundcube yalnızca localhost'tan erişilebilir. Dış dünyaya Nginx reverse proxy üzerinden açılacak.
- **`host.docker.internal:host-gateway`**: Docker container'ından host makinedeki Postfix/Dovecot'a ulaşmak için bu "extra host" tanımı gerekli.
- **`ssl://host.docker.internal:993` ve `:465`**: IMAPS ve SMTPS portları. Bunun neden böyle olduğunun hikayesi aşağıda.
> **Endişelenmeyin - `aspell-tr` paketi bulunamıyor uyarısı kozmetik:** Container ilk başladığında logları izliyorsanız `E: Unable to locate package aspell-tr` satırı dikkat çekebilir. Bu, `ROUNDCUBEMAIL_ASPELL_DICTS: tr` ayarı nedeniyle entrypoint script'inin Türkçe yazım sözlüğünü `apt install aspell-tr` ile indirmeye çalışmasından kaynaklanıyor - ama upstream Debian reposunda `aspell-tr` paketi diye birşey yok (sadece birkaç dil için hunspell alternatifi var). Roundcube bu uyarıyı görüp sorunsuz devam ediyor, tek kayıp Türkçe spell-check olmaması. Ben başta "arka tarafta bir şey bozuldu mu" diye paniklemiştim ama webmail tamamen normal çalışıyordu. Container loglarında gördüğünüzde görmezden gelebilirsiniz.
### Roundcube Özel Yapılandırma
`config/custom.config.inc.php`:
```php
['verify_peer' => false, 'verify_peer_name' => false],
];
$config['smtp_conn_options'] = [
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
];
```
### Hata Deneyimleri - Docker ile İmtihanım
İşte burada hikaye gerçekten ilginçleşiyor. Docker container içindeki Roundcube'u host'taki Postfix/Dovecot'a bağlamak, düşündüğümden çok daha zordu. Dört farklı sorunla karşılaştım ve her birinin çözümü saatlerimi aldı.
**Hata 1: "Authentication not allowed until SSL/TLS is enabled"**
İlk yapılandırmada IMAP için port 143'ü kullandım. Ama Dovecot'ta `ssl = required` ayarı olduğu için, TLS olmadan kimlik doğrulamaya izin vermiyordu. Docker container'ı `host.docker.internal:143`'e düz bağlantı yapıyordu ve Dovecot bunu reddediyordu.
**Çözüm:** IMAP bağlantısını `ssl://host.docker.internal:993`'e, SMTP'yi de `ssl://host.docker.internal:465`'e çevirdim. Böylece bağlantı baştan sona şifreli.
**Hata 2: SSL verify_peer hatası**
Port değişikliğinden sonra yeni bir hata çıktı. SSL sertifikası `mail.orneksite.com` için düzenlenmişti ama Roundcube `host.docker.internal` adresine bağlanıyordu. Sertifika adı uyuşmazlığı nedeniyle PHP'nin SSL doğrulaması başarısız oluyordu.
**Çözüm:** Roundcube config'ine `verify_peer` ve `verify_peer_name` seçeneklerini `false` olarak ekledim. Bu, aynı sunucu içi iletişim olduğu için güvenlik riski oluşturmuyor - dışarıya açık bir bağlantı değil!
```php
$config['imap_conn_options'] = [
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
];
$config['smtp_conn_options'] = [
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
];
```
> **Güvenlik notu - `verify_peer = false`'u nerede güvenlidir, nerede değildir:** Bu ayar sadece **localhost üzerinde, host ile Docker container arasındaki iletişimde** güvenli. Çünkü bu bağlantı dışarıdan erişilemez - Docker internal network + `127.0.0.1:8080` bind + Nginx reverse proxy zinciri sayesinde container'ın IMAP/SMTP trafiği hiçbir zaman dış ağa çıkmıyor. Aynı ayarı **dış bir mail sunucusuna** bağlanırken YAPMA - man-in-the-middle saldırısına açık kapı bırakırsın, saldırgan araya girip sertifika değiştirebilir. Roundcube'un dışarıdaki kullanıcı bağlantıları Nginx üzerinden HTTPS ile geliyor ve orada Let's Encrypt sertifikasının doğrulaması tam yapılıyor - bu bölüm ayrı bir katman.
**Hata 3: Fail2ban Docker IP'yi banladı**
Her şey çalışıyor gibi göründü ama bir süre sonra Roundcube tekrar bağlanamaz oldu. Log'lara baktığımda, Fail2ban'ın Docker container'ının IP adresini (192.168.64.3 gibi) banladığını gördüm! Birkaç başarısız giriş denemesi, Roundcube'un kendi IP'sini kara listeye aldırmıştı.
**Çözüm:** Fail2ban jail'lerine Docker subnet'ini (`172.16.0.0/12` ve `192.168.0.0/16`) `ignoreip` listesine ekledim. Bu subnet'lerden gelen bağlantılar asla banlanmaz.
**Hata 4: Gönderici adresi "kullanici1@host.docker.internal"**
Roundcube'dan ilk test mailimi gönderdiğimde, alıcının gördüğü gönderici adresi `kullanici1@host.docker.internal` idi! Roundcube, ilk girişte kullanıcının kimliğini (identity) otomatik oluşturuyor ve bunu hostname'den türetiyordu.
**Çözüm:** Roundcube'un PostgreSQL veritabanına bağlanıp `identities` ve `users` tablolarını elle güncellement gerekti:
```bash
docker exec -it db-postgresql-roundcube psql -U roundcube -d roundcube
```
```sql
-- Kullanıcı email adresini güncelle
UPDATE users SET username = 'ben@orneksite.com' WHERE username LIKE '%kullanici1%';
-- Kimlik bilgilerini güncelle
UPDATE identities SET email = 'ben@orneksite.com', name = 'Ad Soyad' WHERE email LIKE '%kullanici1%';
exit;
```
**Daha temiz bir alternatif - sorunu hiç yaşamamak:** Yukarıdaki SQL düzeltmesi çalışıyor ama her yeni hesap için bunu tekrarlamak epey sıkıcı. Custom config'de `$config['username_domain'] = '';` satırını **boş** bıraktığımda (ki zaten yukarıdaki config'de öyle), kullanıcı login ekranında kısa ad yerine TAM e-posta adresini (`info@orneksite.com`) girmek zorunda kalıyor. Bu durumda Roundcube users tablosuna username'i doğrudan tam e-posta olarak yazıyor ve `@host.docker.internal` tuzağına hiç düşmüyorsunuz. Yan etki kullanıcıların her girişte uzun e-posta yazması ama bu aslında güvenlik açısından da iyi bir pratik: login kimliği = gönderim kimliği eşitliği, phishing saldırılarında kimlik karışıklığını azaltıyor. İlk kurulumlarımda bu düşünmemiştim, ikinci kurulumda bu yöntemle hiç SQL'e girmek zorunda kalmadım.
### Container'ları Başlatma
```bash
cd /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com
docker compose up -d
```
Doğrulama:
```bash
docker compose ps
# Her iki container da "Up" durumunda olmalı
docker compose logs app-roundcube | tail -20
```


## Adım 9: Nginx Reverse Proxy
Roundcube localhost:8080'da çalışıyor. Onu dış dünyaya HTTPS üzerinden açmak için Nginx'i yapılandıralım.
`/etc/nginx/sites-available/mail.orneksite.com`:
```nginx
server {
listen 80;
listen [::]:80;
server_name mail.orneksite.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mail.orneksite.com;
ssl_certificate /etc/letsencrypt/live/mail.orneksite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mail.orneksite.com/privkey.pem;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
client_max_body_size 25m;
access_log /var/log/nginx/mail.orneksite.com.access.log;
error_log /var/log/nginx/mail.orneksite.com.error.log;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
# Hassas dizinlere ve gizli dosyalara erişimi engelle
location ~ /\. { deny all; }
location ~ ^/(config|temp|logs)/ { deny all; }
}
```
Aktifleştirme:
```bash
sudo ln -s /etc/nginx/sites-available/mail.orneksite.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
`nginx -t` komutunun `syntax is ok` ve `test is successful` dediğini görmelisiniz. Görmüyorsanız yapılandırmada bir hata var demektir - reload/restart yapmayın, önce hatayı düzeltin.
> **Küçük sürüm tuzağı - `listen 443 ssl http2;` syntax'ını koruyun:** Yukarıdaki config'de HTTP/2'yi `listen` satırıyla aynı satıra yazdığımı fark etmişsinizdir (`listen 443 ssl http2;`). Yeni nginx dokümantasyonu (1.25.1+) bu syntax'ın deprecated olduğunu söyleyip `listen 443 ssl;` + ayrı satırda `http2 on;` direktifini öneriyor. Ben bir keresinde modern dokümanı okuyup config'i yeni syntax'a çevirdim, `nginx -t` dedim ve `unknown directive "http2"` hatasıyla karşılaştım. Çünkü Ubuntu 24.04 repolarındaki nginx hâlâ **1.24** - yeni `http2 on;` direktifini bilmiyor. Eski satır içi syntax çalışıyor. Ubuntu sürümünüz 24.04 veya altıysa eski syntax'ı koruyun, yeniye geçmeyin.
### SSL Sertifikası: Birden Fazla Domain İçin Genişletme
Başlangıçta SSL sertifikanızı sadece `mail.orneksite.com` için almış olabilirsiniz. İkinci bir domain ekleyince, mail istemcileri `mail.ikincidomain.com`'a bağlandığında sertifika uyuşmazlığı hatası alırsınız. Çözüm: mevcut sertifikayı `--expand` parametresiyle genişletmek.
```bash
sudo certbot certonly --nginx -d mail.orneksite.com -d mail.ikincidomain.com --expand
```
Bu komut mevcut sertifikayı güncelleyerek her iki hostname'i de SAN (Subject Alternative Name) alanına ekler. Sertifika yolu değişmez (`/etc/letsencrypt/live/mail.orneksite.com/`), bu yüzden Postfix ve Dovecot yapılandırmanızda herhangi bir değişiklik yapmanız gerekmez.
Sertifika yenileme hook'u (certbot otomatik olarak yapar ama kontrol edin):
```bash
cat /etc/letsencrypt/renewal/mail.orneksite.com.conf
```
`renew_hook` satırında Postfix ve Dovecot'un yeniden yüklendiğinden emin olun:
```ini
renew_hook = echo "$RENEWED_DOMAINS" | grep -q 'mail.orneksite.com' && service postfix reload && service dovecot reload
```
> **İPUCU:** Ben başta sadece ana domain için sertifika almıştım. İkinci domain'i eklerken bu `--expand` numarasını öğrendim. Sertifika yolu aynı kaldığı için Postfix/Dovecot config'lerine dokunmam gerekmedi - çok pratik!
## Adım 10: Postfix'i Aktif Etme
Tüm yapılandırmalar hazır. Şimdi Postfix'i çalıştıralım. Ama önce bir kontrol:
```bash
sudo postfix check
```
Eğer hata veriyorsa, her hatayı teker teker düzeltmeniz gerekiyor.
```bash
# Örneğin: postfix/postfix-script: warning: not owned by root: /etc/postfix/./dkim/ikincidomain.com/mail.private -> Bu hatayı alırsanız...
# Şu şekilde düzeltebilirsiniz:
chown root:root /etc/postfix/dkim/ikincidomain.com/mail.private && ls -la /etc/postfix/dkim/ikincidomain.com/mail.private
grep -r "ikincidomain.com" /etc/opendkim* 2>/dev/null; ls -la /etc/postfix/dkim/ikincidomain.com/ 2>&1
chown root:opendkim /etc/postfix/dkim/ikincidomain.com/mail.private && chmod 640 /etc/postfix/dkim/ikincidomain.com/mail.private && ls -la /etc/postfix/dkim/ikincidomain.com/mail.private
# tekrar check edin
```
Check komut herhangi bir hata mesajı vermezse her şey yolunda demektir.
```bash
sudo systemctl enable postfix
sudo systemctl restart postfix
```
Port kontrolü:
```bash
ss -tlnp | grep -E ':(25|587|465)\s'
```
25, 587 ve 465 portlarının dinlendiğini (LISTEN) görmelisiniz. Bu çıktıyı görmek, projenin yarısının tamamlandığı anlamına geliyor.
> **`ss -tlnp` çıktısını nasıl okumalı:** `LISTEN` state = port dinliyor, yani o port üzerinden gelen bağlantıları kabul etmeye hazır. Eğer portlar listede görünmüyorsa Postfix çalışmıyor ya da farklı bir arayüze bind olmuş olabilir - `sudo systemctl status postfix` ile durumu kontrol et (`active (running)` = servis çalışıyor, `inactive` = kapalı, `failed` = başlatılmaya çalıştı ama crash oldu, `journalctl -u postfix -n 50` ile son 50 log satırı).
### Ubuntu 24.04'te `/var/log/mail.log` neden boş? (Hayatımı kaydeden 15 dakika)
Bu beni ilk seferinde uzun süre yanılttı. Postfix servisi `active (running)`, `ss` ile portlar LISTEN halinde, test mailleri teslim oluyor... ama `tail /var/log/mail.log` diyorum, "No such file or directory". Log yok! "Servis çalışmıyor olabilir mi?" diye defalarca yeniden başlattım. Meğer Ubuntu 24.04'te iki ayrı "default" ayarı birleşip bu dosyanın hiç oluşmamasına yol açıyor:
**Sebep 1 - systemd-journald'ın syslog'a iletimi kapalı:** Ubuntu 24.04'te `systemd-journald.conf` varsayılan olarak `ForwardToSyslog=no` ile geliyor. Yani mail facility'sine ait log satırları journal'da duruyor, `rsyslog`'a hiç iletilmiyor - bu yüzden `rsyslog`'un mail.log dosyasını doldurması mümkün olmuyor. Açmak için:
```bash
sudo sed -i 's|^#\?ForwardToSyslog=.*|ForwardToSyslog=yes|' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald
sudo systemctl restart rsyslog
```
**Sebep 2 - rsyslog'un `/var/log/` dizinine yazma izni yok:** `rsyslog` servisi `syslog` kullanıcısı olarak çalışıyor ve `/var/log/` dizinine olmayan bir dosyayı oluşturma yetkisi yok. Yani journal'dan akış gelse bile dosyayı kendi yaratamıyor. Çözüm: dosyaları root olarak önceden oluşturup sahipliğini syslog'a vermek.
```bash
sudo touch /var/log/mail.{log,err,warn,info}
sudo chown syslog:adm /var/log/mail.{log,err,warn,info}
sudo chmod 640 /var/log/mail.{log,err,warn,info}
```
Bu iki adımı atlarsanız, fail2ban'ın bazı jail'leri (örneğin `postfix-sasl`) `/var/log/mail.log`'u bulamadığı için hiç başlamıyor. "fail2ban neden bu jail'de hata veriyor" diye 15 dakikamı yedi bu durum. Önce logları konuşturun, sonra fail2ban'a geçin.
> **Geri alma planı:** Eğer bir şeyler ters giderse, yedeklerinizden geri dönebilirsiniz: `sudo cp -r /etc/postfix.backup.* /etc/postfix && sudo systemctl restart postfix`
## Adım 11: Firewall (UFW)
Sunucumuzun dış dünyayla hangi portlardan konuşacağını kontrol edelim:
```bash
# SSH (zaten eklemiş olmalısınız)
sudo ufw allow 22/tcp
# SMTP - Sunucular arası mail alışverişi
sudo ufw allow 25/tcp
# IMAP - Mail istemcileri için (STARTTLS)
sudo ufw allow 143/tcp
# HTTPS - Roundcube web arayüzü
sudo ufw allow 443/tcp
# SMTPS - Mail gönderme (SSL/TLS)
sudo ufw allow 465/tcp
# Submission - Mail gönderme (STARTTLS)
sudo ufw allow 587/tcp
# IMAPS - Mail okuma (SSL/TLS)
sudo ufw allow 993/tcp
# Firewall'u etkinleştir
sudo ufw enable
# Durumu kontrol et
sudo ufw status verbose
```
> **Uyarı:** Port 22 (SSH) eklemeyi MUTLAKA firewall'u enable etmeden önce yapın. Aksi takdirde sunucuya erişiminizi kaybedersiniz ve Hetzner konsolundan müdahale etmeniz gerekir. (Ben yandım siz yanmayın :))
## Adım 12: Hetzner Port 25 Engeli
Bu, tüm sürecin en sinir bozucu anıydı. Her şeyi yapılandırdım, DNS kayıtları doğru, Postfix çalışıyor, Dovecot çalışıyor, Roundcube çalışıyor... Ama Gmail'e mail gönderemiyorum. Log'lara baktım: **"Connection timed out"**.
Saatlerce neyin yanlış olduğunu anlamaya çalıştım. Firewall'u kontrol ettim, Postfix yapılandırmasını yüz kere gözden geçirdim. Sonra fark ettim...
**Hetzner, yeni sunucularda giden port 25'i varsayılan olarak bloke ediyor!**
Bu, spam önleme politikası. Yeni sunucuların spam göndermesini engellemek için makul bir karar ama hiçbir yerde belirgin şekilde belirtilmiyor.
### Nasıl Tespit Edilir
```bash
timeout 5 bash -c 'echo "QUIT" | nc -w 3 gmail-smtp-in.l.google.com 25'
```
Bu komut yanıt vermezse (timeout olursa), port 25 bloklu demektir. Normal çalışıyorsa `220 mx.google.com ESMTP` gibi bir yanıt görürsünüz.
### Çözüm: Hetzner Destek Talebi
[Hetzner destek](https://console.hetzner.com/support) panelinden bir ticket açmanız gerekiyor. İngilizce olarak, profesyonel bir dille yazmanızı öneriyorum. İşte kullanabileceğiniz örnek bir mesaj:
```plaintext
Hello,
I would like to request unblocking of outbound port 25 (SMTP) on my server
(IP: SUNUCU_IP_ADRESINIZ).
I am setting up a legitimate mail server for my domains orneksite.com and
ikincidomain.com with proper SPF, DKIM, DMARC, and PTR records configured.
Thank you.
```
Genellikle birkaç saat içinde yanıt gelir. Hetzner ekibi sunucunuzun DNS kayıtlarını kontrol edip, gerçekten meşru bir mail sunucusu kurduğunuzu teyit ettikten sonra portu açar.
> **İyi haber:** Port açıldıktan sonra, Postfix'in kuyruğunda bekleyen mailler otomatik olarak gönderilmeye başlar. Yani tekrar göndermek zorunda kalmazsınız.

## Adım 13: Fail2ban - Roundcube ve Mail Servisleri Koruması
Brute-force saldırıları, mail sunucularının en sık karşılaştığı tehdittir. Fail2ban, başarısız giriş denemelerini izleyip saldırganları otomatik banlar.
> **Önceden bir dosya oluşturun - Roundcube log'u:** Roundcube'un `logs` volume'ü ilk container başlatıldığında tamamen boş. İçerideki `errors.log` dosyası, ilk Roundcube hatası veya başarısız login olana kadar oluşmuyor. Fail2ban `roundcube-auth` jail'i bu dosyayı `logpath` olarak kullanıyor ve dosya yokken jail başlamayı reddediyor (`Have not found any log file for roundcube-auth jail` hatası). Ben bunu sonradan öğrendim; önce fail2ban'ı kurdum, sonra "jail başlamıyor" diye debug ettim, meğer dosya yokmuş. Fail2ban'ı yapılandırmadan önce boş dosyayı manuel oluşturmak en temiz çözüm:
>
> ```bash
> touch /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com/logs/errors.log
> ```
### Roundcube Jail
`/etc/fail2ban/jail.d/roundcube.conf`:
```ini
[roundcube-auth]
enabled = true
port = http,https
filter = roundcube-auth
logpath = /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com/logs/errors.log
maxretry = 3 # (örnek değer - kendi tercihine göre ayarla)
findtime = 600
bantime = 7200
```
> **NOT:** Yukarıdaki değerler makul bir başlangıç noktası. Sunucundaki gerçek değerleri örn. Git'e commit'lemeden önce düşünmeni öneririm - saldırgan eşik değerini bilirse tam altında kalabilir. Benim canlıdaki değerlerim bu rehberde yazdığımla birebir değil, farklılaştırıyorum.
> **Tuzak 1 - Jail dosyalarının yüklenme sırası:** Bu sinsi bir tuzak ve beni saatlerce uğraştırdı. Fail2ban config dosyalarını şu sırada yüklüyor: `jail.conf` → `jail.d/*.conf` (alfabetik) → `jail.local` → `jail.d/*.local` (alfabetik). Debian/Ubuntu paketleri kurulduğunda `jail.local` içine default bazı bloklar (örneğin `[roundcube-auth]`) koyabiliyor. Sen yukarıdaki gibi custom jail'ini `jail.d/roundcube.conf` olarak yazarsan, `jail.local` senden SONRA yüklendiği için tüm ayarlarını override ediyor. Benim başıma gelen tam olarak buydu - `fail2ban` bana sürekli "Have not found any log file for roundcube-auth jail" hatası veriyordu, ben de "logpath tam doğru be kardeşim!" diye ekrana bakıp duruyordum. Meğer `jail.local`'daki default blok benim `logpath`'imi, fail2ban'ın beklediği ama var olmayan `/var/log/roundcube/errors.log`'a ezmiş. `fail2ban-client -d | grep roundcube` veya `fail2ban-client --test -v` ile yükleme sırasını görene kadar anlamadım.
>
> **Çözüm:** Custom jail'inizi `.conf` yerine `.local` uzantılı yazın ve dosya adını alfabetik sırada sona düşürün. Ben `/etc/fail2ban/jail.d/zzz-roundcube.local` olarak yazıyorum şimdi - bu dosya `jail.local`'dan SONRA yükleniyor ve override savaşını kazanıyor.
> **Tuzak 2 - `roundcube-auth` filter journal backend'ine düşüyor:** Paketle gelen `/etc/fail2ban/filter.d/roundcube-auth.conf` dosyasında `journalmatch = SYSLOG_IDENTIFIER=roundcube` satırı tanımlı. Fail2ban bu satırı görünce `backend = auto` ile bu jail'i systemd journal üzerinden okumaya çalışıyor ve bizim belirttiğimiz file-based `logpath`'i tamamen görmezden geliyor. Roundcube Docker container'ı stdout'a log yazdığı için syslog identifier'ı da yok - sonuç olarak jail sessizce hiç birşey izlemiyor, ban asla tetiklenmiyor.
>
> İki çözüm seçeneği var:
> 1. Jail'e explicit olarak `backend = polling` ekleyin (en hızlı yol)
> 2. `cp /etc/fail2ban/filter.d/roundcube-auth.conf /etc/fail2ban/filter.d/roundcube-auth-file.conf` yapıp kopyadaki `journalmatch` satırını silin, jail'de `filter = roundcube-auth-file` kullanın.
>
> Ben `backend = polling` yazmayı tercih ediyorum, tek satır düzeltiyor meseleyi.
### Mail Servisleri Jail'leri
`/etc/fail2ban/jail.d/emailwiz.local`:
```ini
[postfix]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
[postfix-sasl]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
[sieve]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
[dovecot]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
```
> **Önemli:** Her jail'de `ignoreip` satırına Docker subnet'lerini (`172.16.0.0/12` ve `192.168.0.0/16`) eklemeyi unutmayın. Aksi takdirde Docker container'ının IP'si banlanır ve Roundcube çalışmaz hale gelir - bunu zor yoldan öğrendim.
> **Neden Docker subnet'leri `ignoreip`'te?** Bu detayı açayım çünkü ilk bakışta "iç ağı banlamak zaten olmaz ki" diye düşünebilirsin ama tam tersi oluyor. Roundcube container'ı host'taki Dovecot'a bağlanırken kaynağı `172.x.x.x` yani Docker internal IP'si olarak görünüyor. Kullanıcı Roundcube'dan birkaç yanlış şifre denerse (örn. parolasını unutmuş), fail2ban bu denemeleri görüp Roundcube container'ının iç IP'sini banlıyor. SONUÇTA ROUNDCUBE KENDİ KENDİNİ KİLİTLİYOR - kullanıcı "bir şey bağlanamıyor" diye bakarken fail2ban log'larında container'ın IP'si banlı durur. İç ağ subnet'lerini `ignoreip`'e koymak bu kendi kendini ayağından vurma durumunu önlüyor. Dışarıdan (internet IP'leri) gelen brute-force saldırıları yine normal şekilde banlanıyor - sadece Docker iç ağından gelen trafik whitelist'lenmiş oluyor.
```bash
sudo systemctl restart fail2ban
```
```bash
sudo fail2ban-client status
```
**DİKKAT:** Bu iki komutu ayrı ayrı yazdım çünkü zamanlama nedeniyle restart ve status komutlarını sudo ile aynı satırda çalıştırdığında, socket henüz hazır olmadan status sorgulanmış olur ve şu tarz bi hata alırsınız:
`2026-xx-xx xx:xx:xx,979 fail2ban [3677040]: ERROR Failed to access socket path: /var/run/fail2ban/fail2ban.sock. Is fail2ban running?`
Veya direkt bunu yazın:
```bash
sudo systemctl restart fail2ban && sleep 2 && sudo fail2ban-client status
```
## Laravel ve Web Uygulamalarında Mail Entegrasyonu
Mail sunucunuz çalıştıktan sonra, en sık yapacağınız şeylerden biri onu Laravel (veya benzeri framework) projelerinize bağlamak olacak. Şifre sıfırlama, bildirim mailleri, doğrulama kodları... Hepsi buradan gidecek. Ama burada çok kritik bir tuzak var ki, benim saatlerimi yedi.
### Laravel .env Yapılandırması
```env
MAIL_MAILER=smtp
MAIL_HOST=mail.orneksite.com
MAIL_PORT=465
MAIL_USERNAME=no-reply@orneksite.com
MAIL_PASSWORD=BURAYA_SIFRE
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=no-reply@orneksite.com
MAIL_FROM_NAME="${APP_NAME}"
```
> **Port seçimi:** `465` (SMTPS/SSL) veya `587` (STARTTLS) kullanabilirsiniz. `465` ile `MAIL_ENCRYPTION=ssl`, `587` ile `MAIL_ENCRYPTION=tls` kullanın.
### no-reply Hesabı Oluşturma
Uygulamalar için ayrı bir `no-reply@` hesabı oluşturmanızı şiddetle öneririm. Kişisel mail hesabınızın şifresini `.env` dosyalarına yazmak güvenlik riski oluşturur. İşte adımlar:
```bash
# Sistem kullanıcısı oluştur (tüm no-reply adresleri buna bağlanacak)
sudo useradd -m -s /usr/sbin/nologin noreply
# Mail dizinlerini oluştur
sudo mkdir -p /home/noreply/Mail/{Inbox,Sent,Drafts,Trash,Junk}/{cur,new,tmp}
sudo chown -R noreply:noreply /home/noreply/Mail
```
Şifre hash'i oluşturmak için `doveadm pw` kullanın (shadow dosyasından almak yerine):
```bash
doveadm pw -s CRYPT -p 'BURAYA_GUCLU_SIFRE'
# Çıktı: {CRYPT}$2y$05$...
```
Dovecot users dosyasına ekleyin (`/etc/dovecot/users`):
```
no-reply@orneksite.com:{CRYPT}$2y$05$HASH_BURAYA...:UID:GID::/home/noreply::userdb_mail=maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
```
Postfix dosyalarına da ekleyin:
```bash
# /etc/postfix/virtual
echo "no-reply@orneksite.com noreply" | sudo tee -a /etc/postfix/virtual
sudo postmap /etc/postfix/virtual
# /etc/postfix/login_maps.pcre
echo '/^no-reply@orneksite\.com$/ no-reply@orneksite.com' | sudo tee -a /etc/postfix/login_maps.pcre
```
İzinleri düzeltin ve servisleri yeniden başlatın:
```bash
sudo chmod 644 /etc/dovecot/users
sudo systemctl restart postfix dovecot
```
#### Paylaşımlı Mailbox: Birden Fazla no-reply Adresi
Birden fazla domain'iniz varsa, her biri için ayrı `no-reply@` adresi oluşturabilirsiniz - ama hepsi aynı sistem kullanıcısına (ve dolayısıyla aynı mailbox'a) bağlı olabilir. Sadece şifreleri farklı olsun:
```
# /etc/dovecot/users
no-reply@orneksite.com:{CRYPT}$2y$05$HASH_1...:1012:1012::/home/noreply::userdb_mail=maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
no-reply@ikincidomain.com:{CRYPT}$2y$05$HASH_2...:1012:1012::/home/noreply::userdb_mail=maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
```
Aynı UID/GID, aynı home dizini → aynı mailbox. Farklı şifreler → güvenlik. Zaten `no-reply` adreslerine gelen maillere yanıt verilmeyeceği için, hepsinin aynı yere düşmesinde bir sakınca yok.
### Gmail Phishing Tuzağı: FROM Domain Eşleşmesi
> **BU BÖLÜMÜ MUTLAKA OKUYUN!** Bu, benim en çok zaman kaybettiğim sorunlardan biri oldu.
İlk başta pratiklik adına tüm projelerimde tek bir `no-reply@orizora.tr` adresi kullandım. `erhanurgun.tr` projesi de dahil. Sonra `erhanurgun.tr`'den bir şifre sıfırlama maili gönderdim ve Gmail şu uyarıyı verdi:
> *"Bu ileti tehlikeli olabilir... Buna benzer iletiler, kişisel bilgileri çalmak için kullanılmıştı."*
Bu bir spam uyarısı değil, **phishing uyarısı**! Gmail'in AI'ı şunu görüyor:
- **Gönderen:** `no-reply@orizora.tr`
- **İçerik:** "erhanurgun.tr'de şifrenizi sıfırlayın" + erhanurgun.tr linkleri
- **Gmail'in yorumu:** "Farklı domain'den gelen, başka domain'de şifre sıfırlatma talebi = **klasik phishing**"
**Kural:** Her projenin `MAIL_FROM_ADDRESS` değeri, o projenin kendi domain'iyle eşleşmelidir!
| Proje | Doğru FROM | Yanlış FROM |
|-------|-----------|-------------|
| orneksite.com | `no-reply@orneksite.com` | `no-reply@baskadomain.com` |
| ikincidomain.com | `no-reply@ikincidomain.com` | `no-reply@orneksite.com` |
Bu demek oluyor ki: her domain için mail sunucunuzda ayrı bir `no-reply@` hesabı, DKIM anahtarı ve DNS kayıtları (SPF, DKIM, DMARC) gerekiyor. Evet, biraz daha iş ama Gmail'in phishing filtresi affetmiyor.
> **İstisna:** Aynı "aile"deki domain'ler (örneğin `orneksite.com` ve `orneksite.com.tr`) genelde sorun çıkarmaz çünkü Gmail bunları ilişkili olarak algılayabilir. Ama tamamen farklı domain'ler (örneğin `orneksite.com` ve `tamamenfarklı.net`) arasında kesinlikle FROM eşleşmesi yapın.
## Doğrulama ve Test
Her şey yapılandırıldı. Şimdi gerçek hayatta çalışıp çalışmadığını test edelim. Bu anı uzun zamandır bekliyordum ve ilk başarılı testin verdiği mutluluğu tarif etmek güç doğrusu... :D
### DNS Doğrulama
```bash
# MX kaydı
dig MX orneksite.com +short
# Beklenen: 10 mail.orneksite.com.
# SPF
dig TXT orneksite.com +short
# Beklenen: "v=spf1 mx a:mail.orneksite.com -all"
# DKIM
dig TXT mail._domainkey.orneksite.com +short
# DMARC
dig TXT _dmarc.orneksite.com +short
# PTR (Reverse DNS)
dig -x SUNUCU_IP_ADRESİNİZ +short
# Beklenen: mail.orneksite.com.
```
### DKIM Anahtar Doğrulama
```bash
sudo opendkim-testkey -d orneksite.com -s mail -vvv
```
`key OK` mesajını görmelisiniz. `key not secure` uyarısı DNSSEC ile ilgili olup normal çalışmayı etkilemez.
> **"key not secure" konusunda paniklemeyin:** İlk gördüğümde "amanın bir şey ters gitti" diye DKIM config'ini baştan sona gözden geçirdim, saatlerimi harcadım. Meğer bu uyarı tamamen kozmetik - kaydınız DNSSEC ile imzalanmadığı için çıkıyor (yani domain registrar'ınızda DS kaydı yoksa). Gmail, Outlook, Yahoo gibi büyük mail sağlayıcılarının hiçbiri DKIM için DNSSEC şartı aramıyor, mail teslimatınızı kesinlikle etkilemiyor. Önemli olan **`key OK`** mesajı - bunu gördüyseniz DKIM iş görüyor demektir. Uyarıdan kurtulmak için Cloudflare'de DNSSEC'i aktif edip registrar'a DS kaydı ekleyebilirsiniz ama bu tamamen opsiyonel, mail delivery için gereksiz bir fazladan adım.
> **DNSSEC açmak istersen (opsiyonel):** Madem bahsettik, adımlarını da vereyim. Cloudflare panel → Domain seç → **DNS** sekmesi → aşağıda "DNSSEC" bölümünde **"Enable DNSSEC"** butonu. Cloudflare sana bir **DS kaydı** (digest, algorithm, key tag) gösterecek. Bu DS kaydını, domain'ini aldığın yerdeki (registrar) kontrol paneline gidip girmen gerekiyor (GoDaddy, Namecheap, Nic.tr, hangisiyse). Propagasyon genelde birkaç saat sürüyor, bazen bir gün. İşlem tamamlandığında `opendkim-testkey` çıktısındaki "key not secure" satırı kaybolur ve yerini "key secure" alır. Tekrarlıyorum: mail delivery için ŞART değil, ama bulabilirsen işi tamamlıyor ve DNS katmanında daha sağlam bir güvenlik sunuyor.
### Fonksiyonel Testler
1. **Roundcube'a giriş**: `https://mail.orneksite.com` adresine gidin. `ben@orneksite.com` ve şifrenizle giriş yapın.
2. **Roundcube'dan Gmail'e mail gönderme**: Hazırla/Compose butonuna tıklayın, bir Gmail adresine test maili gönderin.
3. **Gmail'den Roundcube'a mail alma**: Gmail'den `ben@orneksite.com` adresine bir mail gönderin. Birkaç saniye içinde Roundcube'da görünmeli.
4. **SPF/DKIM/DMARC kontrolü**: Gmail'de gelen maili açın, sağ üstteki üç noktaya tıklayıp "Show original" (Aslını göster) seçin. Başlıklarda şunları aramalısınız:
```
SPF: PASS
DKIM: PASS
DMARC: PASS
```
Bu üç "PASS"ı görmek... Tarif edilemez bir his. Saatlerce (hatta günlerce) uğraştıktan sonra, her şeyin düzgün çalıştığını görmek inanılmaz tatmin ediciydii.
### Dış Doğrulama Araçları
- **MXToolbox**: `mxtoolbox.com` adresinde domain adınızı girin, MX, SPF, DKIM, DMARC kayıtlarınızı doğrulayın.
- **mail-tester.com**: Size verilen adrese bir test maili gönderin, 10 üzerinden puanınızı görün. Hedef: 9/10 veya 10/10.
> **mail-tester.com skorunu nasıl okumalı:** İlk gördüğüm "7.8/10" puan beni paniğe sokmuştu - "bir şey eksik" diye saatlerce config'leri karıştırdım. Meğer skorun nereden düştüğü önemli, sayının kendisi değil. Kabaca sınıflandırma:
>
> - **-0.5 veya -1 puan**: SpamAssassin'in bir-iki detay testi kalemi (mail çok kısa, plain text only, vb.) Bu kadarı doğal ve kabul edilebilir, paniğe gerek yok.
> - **-1 veya daha fazla**: SPF / DKIM / DMARC'tan birinde **fail** var → DNS kayıtlarını derhal kontrol et. Mail-tester raporu tam olarak hangisinin fail olduğunu söyler.
> - **-2 veya daha fazla puan kaybı**: IP reputation sorunu olabilir. Eğer sunucu yepyeniyse biraz sabır - ilk haftalarda birazcık düşük başlar, düzenli trafikle yükselir. Ama haftalar geçiyor ve düzelmiyorsa Spamhaus/SORBS gibi bir RBL'de olup olmadığını kontrol et.
>
> İlk test 7-8/10 çıktı diye rehberin başına dönüp her şeyi yeniden yapma. Önce rapordaki DETAYLI satırları oku, gerçekten fail olan bileşeni tespit et. Sadece 6'nın altına düştüğünde **bariz bir sorun var** demektir.

### Parça Parça Doğrulama - Her Bileşeni Ayrı Ayrı Test Etmek
Kurulumun her aşamasında "acaba burası çalışıyor mu?" diye emin olmak istediğimde kullandığım komutları toplu halde bırakıyorum buraya. Bir yerde sorun çıkarsa bu testlerden hangisinin takıldığına bakıp hızlıca hangi parçanın bozulduğunu anlayabiliyorum:
```bash
# SMTP banner testi - hostname doğru şekilde yansıyor mu?
(echo EHLO local; echo QUIT) | nc 127.0.0.1 25
# SMTPS TLS handshake - sertifika CN'i mail.orneksite.com mu?
timeout 3 openssl s_client -connect 127.0.0.1:465 -brief <<< 'QUIT'
# IMAPS TLS handshake - 993 portu doğru sertifikayla açılıyor mu?
timeout 3 openssl s_client -connect 127.0.0.1:993 -brief <<< 'a1 LOGOUT'
# Dovecot kimlik doğrulama - kullanıcı ve şifre gerçekten çalışıyor mu?
doveadm auth test ben@orneksite.com 'SIFRE'
# Dovecot mailbox listesi - namespace ve özel klasörler (Inbox, Sent vb.) görünür mü?
doveadm mailbox list -u ben@orneksite.com
# DKIM imzalama uçtan uca - yerel teslim edilen maile imza eklendi mi?
printf "Subject: test\nFrom: ben@orneksite.com\nTo: info@ikincidomain.com\n\ngovde\n" | sendmail -t -f ben@orneksite.com
grep -l "DKIM-Signature" /home/kullanici2/Mail/Inbox/new/*
```
Bu komutlar sayesinde "SMTP banner'ım doğru ama IMAPS sertifikası yanlış" veya "auth çalışıyor ama DKIM imzalamıyor" gibi çok spesifik hataları daha yazmadan yakalıyorum. Benim genel yaklaşımım: her adımı tamamladıkça ilgili test komutunu çalıştırıp "buradan emin olundu" diyerek ilerlemek. Sona bırakıp toplu test edince, problem çıkınca hangi katmanda olduğunu bulmak işkenceye dönüyor.


## Karşılaştığım Sorunlar ve Çözümleri (Troubleshooting)
Bu rehberi takip ederken aynı sorunlarla karşılaşabilirsiniz. İşte benim karşılaştığım her hata ve çözümü:
| # | Sorun | Hata Mesajı / Belirti | Çözüm |
|---|-------|----------------------|-------|
| 1 | IMAP bağlantı hatası | "Authentication not allowed until SSL/TLS is enabled" | Roundcube'da IMAP bağlantısını `ssl://host.docker.internal:993` olarak değiştirin |
| 2 | Docker'dan host'a bağlantı kesildi | "Connection refused" veya timeout | Fail2ban'ın Docker container IP'sini banlayıp banlamadığını kontrol edin: `sudo fail2ban-client status dovecot` |
| 3 | Gönderici adresi yanlış | Mail `kullanici1@host.docker.internal` adresinden gelmiş görünüyor | Roundcube DB'sinde `users` ve `identities` tablolarını güncelleyin |
| 4 | Gmail'e mail gönderilemiyor | Log'larda "Connection timed out" (port 25) | Hetzner destek'ten port 25 açılmasını talep edin |
| 5 | Sieve izin hatası | "sieve: Failed to stat" veya "Permission denied" | `chmod 755 /var/lib/dovecot/sieve && chmod 644 /var/lib/dovecot/sieve/*.sieve` |
| 6 | Dovecot passwd-file okunamıyor | "Permission denied" users dosyası | `chown root:dovecot /etc/dovecot/users && chmod 640 /etc/dovecot/users` |
| 7 | Gelen mail teslim edilemiyor | Dovecot LDA "user not found" hatası | Dovecot config'e fallback `userdb { driver = passwd }` ekleyin |
| 8 | SMTP mail gönderilemiyor | `[451] 4.7.1 Service unavailable - try again later` | OpenDKIM key dosyası izin sorunu. DKIM private key'leri `opendkim:opendkim` sahipliğinde ve `600` modunda olmalı. `opendkim` grubunda `postfix` gibi ek kullanıcılar varsa OpenDKIM "key data is not secure" diyerek maili reddeder. Çözüm: `chown opendkim:opendkim /etc/postfix/dkim/*/mail.private && chmod 600 /etc/postfix/dkim/*/mail.private` |
| 9 | Dovecot LDA izin hatası | `euid=65534(nobody)` -- `stat(/root/Mail/Inbox/tmp) failed: Permission denied` | `postmaster` ve `root` alias'ları gerçek mail kullanıcısına yönlendirilmemiş. `/etc/aliases` dosyasında `postmaster: kullanici1` ve `root: kullanici1` ekleyip `sudo newaliases` çalıştırın |
| 10 | Dovecot stats-writer hatası | `net_connect_unix(/run/dovecot/stats-writer) failed: Permission denied` | `/etc/dovecot/conf.d/10-master.conf` dosyasına `service stats { unix_listener stats-writer { mode = 0660; group = dovecot } }` bloğunu ekleyip Dovecot'u yeniden başlatın |
| 11 | MySQL başlatılamıyor | `ExecStartPre=... status=203/EXEC` | MySQL systemd dosyaları silinmiş veya paket yarı bozuk (`rH` durumunda). `systemctl daemon-reload` yapın. Eğer service dosyası yoksa paketi `dpkg --purge --force-remove-reinstreq` ile temizleyip yeniden kurun. GPG key süresi dolmuşsa: yeni key'i `/usr/share/keyrings/mysql-archive-keyring.gpg` olarak kaydedin ve repo dosyasına `[signed-by=...]` ekleyin |
| 12 | Ubuntu 24.04 servis adları | `Unit spamassassin.service not found` veya `Unit sshd.service not found` | Ubuntu 24.04'te servis adları değişti: `spamassassin` -> **`spamd`**, `sshd` -> **`ssh`**. Tüm `systemctl` komutlarında yeni adları kullanın |
| 13 | Postfix DKIM key uyarısı | `not owned by root: /etc/postfix/./dkim/*/mail.private` | DKIM key dosyaları root'a ait değilse `postfix check` uyarı verir. Sahipliği `opendkim:opendkim` (600) yaparak hem Postfix hem OpenDKIM'i memnun edin |
| 14 | APT Docker repo çift tanımlı | `Target Packages ... is configured multiple times` | Docker repo'su birden fazla dosyada tanımlanmış. `/etc/apt/sources.list.d/` altındaki duplicate dosyayı silin, sadece `signed-by` içeren dosyayı tutun |
| 15 | Mailler spam'a düşüyor (Received stripping) | SpamAssassin loglarında `NO_RECEIVED, NO_RELAYS` bayrakları | `header_checks`'ten `Received` stripping kuralını kaldırın. Bu kural tüm yönlendirme başlıklarını siler ve spam filtrelerini tetikler. Sadece `X-Originating-IP` satırını bırakın |
| 16 | Gmail "Bu ileti tehlikeli olabilir" (phishing) | Phishing uyarısı, spam değil. "Kişisel bilgileri çalmak için kullanılmıştı" mesajı | FROM domain ile mail içeriğindeki linkler aynı domain olmalı. `no-reply@A.com`'dan gönderip içerikte `B.com` linkleri varsa Gmail bunu phishing olarak algılar. Her proje kendi domain'inden mail göndermeli |
| 17 | Dovecot tüm hesaplarda "Oturum açılamadı" (düzenleme sonrası) | `open(/etc/dovecot/users) failed: Permission denied (euid=113 missing +r perm)` | `/etc/dovecot/users` dosyasını düzenlemek izinleri değiştirebilir. Her düzenlemeden sonra `chmod 644 /etc/dovecot/users` çalıştırın |
| 18 | Çoklu domain SSL sertifika hatası | Mail istemcisinde sertifika uyuşmazlığı hatası | `certbot certonly --nginx -d mail.domain1.com -d mail.domain2.com --expand` ile mevcut sertifikaya yeni domain ekleyin |
### Log Dosyaları - En İyi Arkadaşınız
Bir şeyler yanlış gittiğinde, cevap her zaman log'lardadır:
```bash
# Postfix logları
sudo tail -f /var/log/mail.log
# Dovecot logları
sudo tail -f /var/log/mail.log | grep dovecot
# Fail2ban logları
sudo tail -f /var/log/fail2ban.log
# Roundcube logları
tail -f /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com/logs/errors.log
# Nginx logları
sudo tail -f /var/log/nginx/mail.orneksite.com.error.log
```
## Güvenlik Sıkılaştırma
Mail sunucusu kurulumu bitti ama güvenlik konusunda dikkatli olmak gerekiyor. Bir mail sunucusu, internete doğrudan açık olduğu için sürekli saldırı altında olacak. İşte alınması gereken önlemler:
SSH Güvenliği
Root login'i kapatın ve sadece SSH key ile girişe izin verin:
```bash
sudo vim /etc/ssh/sshd_config
```
```
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
```
```bash
sudo systemctl restart ssh
```
> **Dikkat (Ubuntu 24.04):** SSH servis adı `sshd` değil, **`ssh`** olarak değişti. `systemctl restart sshd` derseniz `Unit not found` hatası alırsınız. Ayrıca Ubuntu 24.04'te SSH, socket activation (`ssh.socket`) ile çalışır -- bu normaldir.
> **Uyarı:** Bu ayarları yapmadan ÖNCE SSH key'inizi sunucuya yüklediğinizden emin olun. Aksi takdirde sunucuya erişiminizi kaybedersiniz.
İsteğe bağlı olarak SSH portunu değiştirebilirsiniz (22'den farklı bir port), ama bu güvenlik açısından çok az fark yaratır. Fail2ban zaten brute-force denemelerini engeller.
Kurulum Sonrası Düz Metin Şifre Temizliği
Kurulum sırasında kaçınılmaz olarak düz metin şifreler elinizden geçer: `sudo passwd` için yazdığınız şifre, `doveadm pw -p 'SIFRE'` komutunun parametresi, `docker-compose.yml`'deki `POSTGRES_PASSWORD`, kurulumu takip ederken "sonra hash'lerim" diye kenara not ettiğiniz scratch dosya... Kurulum **bittikten sonra** bunların diskte izi kalmamalı. Aksi halde sunucuya yerel okuma erişimi kazanan saldırgan (veya backup'ınıza erişen biri) hazır açılmış bir kutuya bakıyor olur.
**Kontrol edilecek yerler:**
```bash
# 1. "Geçici olarak tutayım" scratch dosyaları
ls -la /root/*.txt /root/.*passwords* /tmp/*pass* 2>/dev/null
# 2. Bash history (şifreyi komut satırında yazdıysanız, örn. doveadm pw -p 'xxx')
grep -iE 'passwd|doveadm pw -p|PASSWORD=' ~/.bash_history 2>/dev/null
# 3. Editor cache'leri: nano .save, vim .swp, .viminfo
find /root -maxdepth 2 \( -name '*.swp' -o -name '*.save' -o -name '.viminfo' \) 2>/dev/null
```
**Güvenli silme: neden `rm` değil `shred`?**
`rm` sadece inode kaydını siler, dosyanın içeriğini diskte olduğu gibi bırakır. Üzerine başka veri yazılana kadar `extundelete`, `photorec` gibi araçlarla geri getirilebilir. `shred -u` dosyayı rastgele veriyle birkaç kez üzerine yazıp sonra bağlantıyı kaldırır:
```bash
shred -u /root/.mail-setup-passwords.txt
```
Bash history için:
```bash
history -c && shred -u ~/.bash_history && touch ~/.bash_history
```
> **Uyarı (modern dosya sistemleri):** Ext4 default'ta `shred` çoğu senaryoda işini görür. Ama COW (Copy-on-Write) kullanan BtrFS/ZFS veya TRIM'in aktif olduğu SSD'lerde `shred`'in üzerine yazdığı blok her zaman hedef bloka denk gelmeyebilir; yani garanti sıfır değil. En güvenli yaklaşım: şifreyi **hiç** diske yazmamak. `doveadm pw`'yi parametresiz çağırın (stdin'den okur ve history'ye düşmez), `docker-compose.yml`'de şifreyi `${VAR}` ile env'den okutun (`.env`'i `.gitignore`'a ekleyin), kurulum biter bitmez şifreyi bir parola yöneticisine (Bitwarden, 1Password, KeePassXC) aktarıp yerelden silin.
**Benim rutinim:** Her hesap için `openssl rand -base64 15` ile rastgele şifre üretiyorum → anında parola yöneticisine yapıştırıyorum → oradan kopyalayıp `doveadm pw`'nin stdin'ine veriyorum → üretilen hash `/etc/dovecot/users`'a. Terminalde, dosyada ve history'de düz metin hiç görünmüyor. Kurulum sonunda silecek bir şey de kalmıyor.
Postfix Güvenlik Ayarları
Bunların çoğu zaten yapılandırmamızda var, ama kontrol edelim:
- **`smtpd_helo_required = yes`**: İstemcinin kendini tanıtmasını zorunlu kılar.
- **`reject_unknown_sender_domain`**: Var olmayan domain'lerden gelen mailleri reddeder.
- **`reject_unknown_reverse_client_hostname`**: PTR kaydı olmayan sunucuları reddeder.
- **`smtpd_tls_auth_only = yes`**: Şifreleri yalnızca TLS üzerinden kabul eder.
- **`smtpd_forbid_bare_newline = normalize`**: SMTP smuggling saldırılarına karşı koruma.
- **`smtpd_sasl_security_options = noanonymous, noplaintext`**: Anonim ve düz metin kimlik doğrulamayı devre dışı bırakır.
Dovecot Güvenliği
- **`ssl = required`**: Tüm bağlantılarda SSL/TLS zorunlu.
- **`ssl_min_protocol = TLSv1.2`**: TLS 1.0 ve 1.1 devre dışı.
- **Güçlü cipher listesi**: Zayıf şifreleme algoritmaları devre dışı (3DES, RC4, MD5, vb.)
- **`ssl_prefer_server_ciphers = yes`**: Sunucunun cipher tercihini zorunlu kılar.
Brute-force Koruması (Fail2ban Detayları)
Mevcut jail'lerimizi gözden geçirelim:
| Jail | Koruduğu Servis | Açıklama |
|------|-----------------|----------|
| postfix | Postfix SMTP | Hatalı SMTP komutları |
| postfix-sasl | Postfix SASL | Başarısız SMTP kimlik doğrulama |
| dovecot | Dovecot IMAP/POP3 | Başarısız IMAP/POP3 giriş denemeleri |
| sieve | Dovecot Sieve | Başarısız ManageSieve giriş denemeleri |
| roundcube-auth | Roundcube | Başarısız webmail giriş denemeleri |
Önerilen başlangıç ayarları:
```ini
maxretry = 3 # (örnek değer - kendi tercihine göre ayarla)
findtime = 600 # 10 dakikalık pencere
bantime = 7200 # 2 saat banlı tut
```
Agresif saldırılar altındaysanız, `bantime`'ı artırabilirsiniz. Hatta `bantime = -1` ile kalıcı ban uygulayabilirsiniz. Yalnız hatırlatırım: gerçek değerlerini Git'e commit'lemeden veya herkese açık bir yerde paylaşmadan düşün, tahmin ederek çalışan saldırganlar eşik değerlerinin bir altında kalmaya uğraşır.
Aktif banları görüntüleme:
```bash
sudo fail2ban-client status postfix-sasl
sudo fail2ban-client status dovecot
sudo fail2ban-client status roundcube-auth
```
SpamAssassin Yapılandırması
SpamAssassin zaten Postfix'e entegre - gelen mailleri otomatik olarak puanlıyor. Spam eşik değerini ayarlamak için:
```bash
sudo vim /etc/spamassassin/local.cf
```
```
required_score 5.0
rewrite_header Subject [SPAM]
report_safe 0
```
- `required_score 5.0`: 5.0 ve üzeri puan alan mailler spam olarak işaretlenir. Varsayılan 5.0 çoğu durum için uygun. Daha agresif filtreleme için 3.0-4.0 arası kullanabilirsiniz.
- `rewrite_header`: Spam maillerin konu satırına [SPAM] etiketi eklenir.
- `report_safe 0`: Orijinal mail korunur, ek olarak spam raporu eklenir.
```bash
sudo systemctl restart spamd
```
Nginx Güvenlik Başlıkları
Yapılandırmamızda zaten eklediğimiz başlıklar:
- **`Strict-Transport-Security`**: Tarayıcıya her zaman HTTPS kullanmasını söyler (HSTS).
- **`X-Frame-Options "SAMEORIGIN"`**: Clickjacking saldırılarını önler.
- **`X-Content-Type-Options "nosniff"`**: MIME type sniffing'i engeller.
Ayrıca hassas dizinlere erişimi engelledik:
```nginx
location ~ /\. { deny all; } # Gizli dosyalar (.env, .git, vb.)
location ~ ^/(config|temp|logs)/ { deny all; } # Yapılandırma ve log dizinleri
```
Open Relay Kontrolü
Sunucunuzun bir "open relay" olmadığından (yani herkesin sizin sunucunuz üzerinden mail gönderebildiği bir durumda olmadığından) emin olun. Open relay, sunucunuzun spam gönderme aracı olarak kullanılmasına yol açar ve IP'nizin kara listeye alınmasıyla sonuçlanır.
Test:
```bash
# Başka bir sunucudan veya yerel makinenizden
telnet mail.orneksite.com 25
EHLO test.com
MAIL FROM:
RCPT TO:
```
Eğer `554 5.7.1: Relay access denied` hatası alıyorsanız - mükemmel! Sunucunuz open relay değil.
Yapılandırmamızdaki `smtpd_relay_restrictions = permit_sasl_authenticated, reject_unauth_destination` satırı bunu sağlıyor. Yani sadece kimlik doğrulaması yapmış kullanıcılar başka domain'lere mail gönderebilir.
Rate Limiting
Brute-force saldırılarını yavaşlatmak için rate limiting uygulayın:
**Roundcube:**
```php
$config['login_rate_limit'] = 5; // 1 dakikada maksimum 5 giriş denemesi
```
Bu zaten `custom.config.inc.php` dosyamızda var.
**Postfix** (isteğe bağlı, `main.cf`'ye eklenebilir):
```ini
smtpd_client_connection_rate_limit = 10
smtpd_client_message_rate_limit = 30
anvil_rate_time_unit = 60s
```
Düzenli Güncellemeler
Mail sunucusu, sürekli güncellenmesi gereken bir sistemdir. Güvenlik yamaları çıktığında hemen uygulanmalıdır.
Otomatik güvenlik güncellemeleri için:
```bash
sudo nala install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
```
Veya düzenli olarak elle güncelleme:
```bash
sudo nala update && sudo nala upgrade -y
```
Docker imajlarını güncelleme:
```bash
cd /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com
docker compose pull
docker compose up -d
```
Log İzleme
Takip etmeniz gereken log dosyaları:
| Dosya | İçerik |
|-------|--------|
| `/var/log/mail.log` | Postfix ve Dovecot tüm aktiviteler |
| `/var/log/fail2ban.log` | Ban/unban kayıtları |
| `/var/log/nginx/mail.orneksite.com.access.log` | Roundcube web erişimleri |
| `/var/log/nginx/mail.orneksite.com.error.log` | Nginx hataları |
| Roundcube `logs/errors.log` | Roundcube uygulama hataları |
Canlı izleme:
```bash
sudo tail -f /var/log/mail.log
```
Belirli bir dönemdeki hataları arama:
```bash
sudo grep "error\|warning\|fatal" /var/log/mail.log | tail -50
```
DMARC'ı Kademeli Sıkılaştırma (Gradual Rollout)
Rehberde DNS kayıtlarını yaparken DMARC'ı `p=quarantine` ile başlattık ve bu pek çok durum için makul bir orta yol. Ama tamamen doğrusu, "book smart" olanı, DMARC'ı **`p=none` ile başlatıp kademeli sıkılaştırmak**. Ben ilk kurulumumda direkt `p=quarantine` yazdım ve ilk günlerde legitimate birkaç mailim (özellikle bir SaaS'ın bir subdomain'den gönderdiği bildirimler) karantinaya düştü, sonra SPF'e ekleyerek düzelttim. İkinci kurulumumda aşağıdaki süreci izleyeceğim:
1. **`p=none`** (ilk 2-4 hafta): `v=DMARC1; p=none; rua=mailto:postmaster@orneksite.com; fo=1`
- Uyum bozuklukları sadece raporlanır, mail teslimi hiçbir şekilde etkilenmez.
- `rua=mailto:...` adresine günlük/haftalık toplu raporlar gelir (Google, Yahoo vb. gönderir).
- Bu raporları okuyarak kendi altyapındaki "legitimate ama SPF/DKIM'den geçemeyen" maillerı tespit edersin. Sık karşılaşılan örnekler: unutulmuş bir SaaS (mesela Mailchimp, Stripe) senin domain'inden mail atıyor olabilir; bu durumda ya o servisin IP bloğunu SPF'e eklemen ya da DKIM imzalama anlaşmasını yapılandırman gerekiyor.
2. **`p=quarantine`** (sonraki 2-4 hafta): `v=DMARC1; p=quarantine; rua=mailto:postmaster@orneksite.com; fo=1`
- Fail'ler alıcının spam klasörüne düşer, tamamen kaybolmaz.
- Kullanıcı/kuruluş geri bildirimi alabilirsin ("şu mailim spam'e düştü") ve reasonably kurtarılabilir.
3. **`p=reject`** (son): `v=DMARC1; p=reject; rua=mailto:postmaster@orneksite.com; fo=1`
- Fail'ler doğrudan reddedilir - karantinaya bile gitmez.
- Bu aşamaya geçmek için, raporlarda artık beklenmedik bir legitimate fail görmemen gerekiyor.
Bu geçişler tamamen **DNS tarafında** yapılır - sunucunda tek bir satır bile değiştirmene gerek yok, sadece Cloudflare'deki `_dmarc` TXT kaydını güncelliyorsun. Rapor analizi için [dmarcian](https://dmarcian.com) veya [postmarkapp.com/dmarc](https://dmarc.postmarkapp.com) gibi ücretsiz rapor parser'lar kullanabilirsin - XML raporları elle okumak zor.
Düzenli Bakım Kontrol Listesi
Mail sunucusu "bir kez kurdum ve unuttum" olacak bir şey değil. Başta öyle sanmıştım, sonra öğrendim. Haftada ya da ayda bir bakacağın kontroller:
- **Sertifika yenileme testi** (ayda bir): `sudo certbot renew --dry-run` - kuru test, gerçek yenileme yapmaz ama prosesin çalışıp çalışmadığını gösterir. certbot cron'u zaten otomatik yeniliyor ama gözle bir gör, hook'ların çalıştığından emin ol.
- **Mail kuyruğu kontrolü** (haftada bir): `sudo postqueue -p` ile takılı mail var mı bak. Eğer çok birikme varsa (onlarca+), alıcı sunucularına bağlanamıyor olabilirsin - DNS ya da ağ sorunu. `sudo postqueue -f` kuyruğu zorla flush eder.
- **Fail2ban durumu** (haftada bir): `sudo fail2ban-client status` → her jail'de banlı IP sayısı. Ani yükseliş, saldırı altında olduğun anlamına gelir, log'ları incele.
- **Disk kullanımı** (haftada bir): `sudo du -sh /home/*/Mail /var/log /opt/roundcube` (veya kendi roundcube dizinin). Mail kutularının ve logların büyümesini takip et; kullanıcıya "kutunuz şişti" diye uyarı çekmeden önce sen bil.
- **Log temizliği** (otomatik, teyit et): Rsyslog default'ta rotation yapar (`/etc/logrotate.d/rsyslog`). Üzerine ek bir şey gerekmez ama `sudo logrotate -d /etc/logrotate.conf` ile kuru çalıştırıp bir kez teyit et.
- **mail-tester skoru** (ayda bir): Yeni bir mail-tester adresi alıp bir test mail gönder, skorun hâlâ 9-10 arası olduğundan emin ol. IP reputation zamanla değişebilir.
- **Yeni bir hesap eklemek ya da şifre değiştirmek** (ihtiyaç oldukça): Rehberin "Yeni Mail Hesabı Ekleme" bölümünü takip et.
Ben bu kontrolleri aklımda tutmak yerine takvime ayarladım - haftada bir Pazartesi sabah 10 dakikam bu işe gidiyor. Alışkanlık oluşturursan rutin bir iş, oluşturmazsan "sürpriz" olarak sorun çıkıyor.
## Yeni Mail Hesabı Ekleme
Her şey çalıştıktan sonra en sık yapacağınız işlem yeni bir mail hesabı eklemek olacak. Diyelim ki `yeni@orneksite.com` adresinde bir hesap açmak istiyorsunuz. İzlemeniz gereken adımlar:
### 1. Sistem Kullanıcısı Oluşturun
```bash
sudo useradd -m -s /usr/sbin/nologin -G mail yenikullanici
sudo passwd yenikullanici
```
Mail dizinlerini oluşturun:
```bash
sudo mkdir -p /home/yenikullanici/Mail/{Inbox,Sent,Drafts,Trash,Junk}
sudo chown -R yenikullanici:yenikullanici /home/yenikullanici/Mail
```
### 2. Dovecot Users Dosyasına Ekleyin
Şifre hash'ini oluşturmanın iki yolu var:
**Yöntem 1: `doveadm pw` ile (önerilen)**
```bash
doveadm pw -s CRYPT -p 'GUCLU_SIFRE_BURAYA'
# Çıktı: {CRYPT}$2y$05$...
```
**Yöntem 2: Mevcut sistem kullanıcısının shadow hash'ini kullanma**
```bash
sudo grep yenikullanici /etc/shadow | cut -d: -f2
```
UID ve GID'yi öğrenin:
```bash
id yenikullanici
```
`/etc/dovecot/users` dosyasına yeni satır ekleyin:
```
yeni@orneksite.com:{CRYPT}$y$j9T$HASH_BURAYA...:UID:GID::/home/yenikullanici::userdb_mail=maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
```
### 3. Postfix Virtual Alias Ekleyin
`/etc/postfix/virtual` dosyasına:
```
yeni@orneksite.com yenikullanici
```
Hash'i yeniden oluşturun:
```bash
sudo postmap /etc/postfix/virtual
```
### 4. login_maps.pcre'ye Ekleyin
`/etc/postfix/login_maps.pcre` dosyasına:
```
/^yeni@orneksite\.com$/ yeni@orneksite.com
```
### 5. Servisleri Yeniden Başlatın
```bash
sudo systemctl restart dovecot
sudo systemctl restart postfix
```
### 6. Test Edin
Roundcube'a `yeni@orneksite.com` ve belirlediğiniz şifreyle giriş yapın. Eğer giriş başarılıysa, bir test maili göndererek her şeyin çalıştığını doğrulayın.
> **NOT:** Eğer farklı bir domain için hesap ekliyorsanız (örneğin `yeni@ikincidomain.com`), DKIM anahtarlarının o domain için de üretilmiş ve DNS'e eklenmiş olduğundan emin olun. Ayrıca `mydestination` satırında o domain'in tanımlı olması gerekiyor. (mydestination'ı unuttuysan yazıda ara!)
> **İPUCU:** Mevcut bir domain'e yeni bir adres ekliyorsanız, DNS kayıtlarında (SPF, DKIM, DMARC, MX) herhangi bir değişiklik yapmanız gerekmez. Bu kayıtlar domain bazında çalışır, adres bazında değil.
> **NOT:** Yeni hesap açmak yerine sadece harici bir adrese yönlendirme yapmak istiyorsanız, hemen aşağıdaki "E-Posta Yönlendirme (Forwarding)" bölümüne bakın. Basit forward'tan catch-all'a kadar tüm senaryoları anlatıyorum.
## E-Posta Yönlendirme (Forwarding)
Mail sunucusu çalıştırmaya başladıktan sonra fark ettim ki bazı durumlarda yeni bir mail kutusu açmak yerine, gelen maili başka bir adrese **iletmek** çok daha pratik. Mesela `info@orneksite.com` adresine gelen müşteri maillerini hem yerel kutuda saklamak istiyorum (arşiv ve disaster recovery için), hem de telefonumdan kolayca görebilmek için Gmail hesabıma kopyalamak istiyorum. Ya da tatildeyken `support@orneksite.com`'a gelen tüm maileri ekipteki başka bir arkadaşıma yönlendirmek istiyorum...
Hosting günlerinde bunu cPanel'in "Forwarders" sekmesinden tek tıkla yapıyordum. Aslında o güzel arayüzün arkasında dönen iş, **Postfix'in `virtual_alias_maps` özelliğinden** başka bir şey değil. Ve bizim sunucumuzda bu zaten kurulu, sadece ne yazacağımızı bilmemiz yeterli. :)
Bu bölümde 4 farklı senaryo göstereceğim, kendi ihtiyacınıza uyanı seçip uygulayın. Sonunda da çoğu insanın gözden kaçırdığı kritik bir konuya değineceğim: forward edilen mailler neden Gmail'de spam'e düşer ve `postsrsd` ile bunu nasıl çözeriz...
### Forwarding Nedir, Alias'tan Farkı Ne?
Önce bir terim karmaşasını açalım. Postfix dünyasında "alias" ve "forward" kavramları aynı dosyada tanımlanır ama mantıken farklıdır:
- **Alias:** Bir mail adresini **yerel bir kullanıcıya** çevirir. `info@orneksite.com` adresine gelen mail aslında `kullanici1`'in kutusuna düşer. Yazının üst kısımlarındaki `info@ikincidomain.com kullanici2` örneği bunun ta kendisi.
- **Forward:** Gelen maili **harici bir adrese** (Gmail, Outlook, başka bir domain'deki adres vb.) iletir veya kopyalar.
İkisi de aynı dosyaya yazılır: `/etc/postfix/virtual`. Postfix hedefe bakıp "bu yerel bir kullanıcı mı, yoksa harici bir adres mi?" diye karar verir ve ona göre davranır. Yani teknik olarak forward yapmak için yeni bir altyapı kurmamıza gerek yok, mevcut `virtual` dosyasına satır eklemek yeterli.
> **cPanel/Plesk benzetmesi:** Gmail'in hesap ayarlarındaki "Filters and Blocked Addresses > Create a new filter > Forward it to" akışının **sunucu seviyesindeki karşılığı** budur. Aradaki fark şu: Gmail filter'ları mail kutuya **düştükten sonra** forward yapar (ve bir kopyası kutuda kalır), Postfix `virtual_alias_maps` ise mail **kutuya düşmeden önce** rewriting yapar. Yani teslimat zincirinin daha başında müdahale eder.
### Senaryo 1: Sadece Harici Hedefe Yönlendirme
En basit senaryo: `info@orneksite.com` adresine gelen tüm mailler doğrudan `ornek@gmail.com`'a iletilsin, yerel sunucuda hiç saklanmasın.
`/etc/postfix/virtual` dosyasını açın:
```bash
sudo vim /etc/postfix/virtual
```
Mevcut satırların sonuna ekleyin:
```
# Tüm info@ mailleri Gmail'e ilet, yerel kopya tutma
info@orneksite.com ornek@gmail.com
```
Hash dosyasını yeniden üretin ve Postfix'i yeniden yükleyin:
```bash
sudo postmap /etc/postfix/virtual
sudo systemctl reload postfix
```
> **`reload` mı `restart` mı?** Bu tarz alias değişikliklerinde `reload` yeterli ve daha doğru tercih. `restart` Postfix'i tamamen durdurup başlatır, kuyruğu işleyen child process'leri öldürür. O sırada teslim edilmekte olan mailler varsa rahatsız edilmiş olurlar. `reload` ise sadece config'i yeniden okur, açık bağlantıları bozmaz.
Test için Gmail'den `info@orneksite.com`'a bir mail atın ve mail.log'u izleyin:
```bash
sudo tail -f /var/log/mail.log | grep info@orneksite.com
```
Beklenen log akışı şöyle olmalı:
```log
postfix/smtpd[...]: NOQUEUE: client=mail-...google.com[...]
postfix/cleanup[...]: ...: message-id=<...@mail.gmail.com>
postfix/qmgr[...]: ...: from=, size=..., nrcpt=1 (queue active)
postfix/smtp[...]: ...: to=, orig_to=, relay=...gmail-smtp-in.l.google.com[...]:25, ..., status=sent
```
Dikkat: `to=, orig_to=` satırı. Postfix `info@`'yu görüp `virtual` dosyasında karşılığını buluyor ve `ornek@gmail.com`'a teslim ediyor. Mail bizim sunucuda hiç saklanmadı.
### Senaryo 2: Hem Yerel Kutuya Hem Harici Adrese Kopyalama
En çok kullandığım senaryo bu. `info@orneksite.com`'a gelen mailin **bir kopyası** Gmail'e iletilsin (gezerken hızlıca okumak için), bir kopyası da yerel kutuda saklansın (Roundcube'tan tam erişim, arşiv, hata durumunda yedek).
`/etc/postfix/virtual` dosyasına virgülle ayrılmış iki hedef yazın:
```
# info@'ya gelen her mail hem yerel kullanıcıya hem Gmail'e gönderilsin
info@orneksite.com kullanici1, ornek@gmail.com
```
Hash güncelle, reload at:
```bash
sudo postmap /etc/postfix/virtual
sudo systemctl reload postfix
```
> **Neden bu en yaygın pattern?** İki sebep var. Birincisi: yerel kopya **disaster recovery**. Bir gün Gmail "hesabınızda olağandışı aktivite" deyip kapatırsa (oluyor, çok oluyor) yerel kutuda her şey hazır duruyor. İkincisi: yerel kopya **arşiv**. Roundcube'tan 5 yıl öncesinin bir mailini aramak Gmail'in arama motorundan daha hızlı oluyor (özellikle çok mail aldığınız bir hesapsa).
Test ettiğinizde mail.log'da iki ayrı teslimat satırı göreceksiniz:
```log
postfix/smtp[...]: ...: to=, orig_to=, ..., status=sent
postfix/local[...]: ...: to=, orig_to=, ..., status=sent
```
İlki harici teslimat (Gmail), ikincisi yerel teslimat (Dovecot LDA üzerinden). Her ikisi de aynı kaynak maili tüketmiş ve tüm alıcılara dağıtılmış oldu.
### Senaryo 3: Çoklu Harici Hedef (Dağıtım Listesi Mantığı)
Küçük bir ekibiniz varsa ve gelen müşteri maillerini herkesin görmesini istiyorsanız, ayrı bir mailing list yazılımı (Mailman, Sympa vb.) kurmanıza gerek yok. `virtual` dosyasında virgülle istediğiniz kadar hedef ekleyebilirsiniz:
```
# support@'a gelen mailler ekipteki herkese dağılsın
support@orneksite.com ali@gmail.com, veli@hotmail.com, ayse@outlook.com
```
Yine standart adımlar:
```bash
sudo postmap /etc/postfix/virtual
sudo systemctl reload postfix
```
> **Dezavantaj, cevaplama hala bireysel:** Bu yöntem mailing list değil, sadece dağıtım. Yani Ali "Reply All" yaptığında cevap müşteriye gider ama Veli ile Ayşe haberdar olmaz. Gerçek bir paylaşımlı gelen kutusu istiyorsanız Mailman veya bir helpdesk yazılımı (Zammad, FreeScout vb.) kurmak gerekiyor. Ama küçük ekipler ve basit kullanım için bu pattern fazlasıyla yeterli.
> **Mail patlama riski:** Hedef listesini çok büyütürseniz (20+ kişi gibi) Gmail/Outlook gibi sağlayıcılar bizi spam göndericisi olarak işaretleyebilir. 5-10 kişilik küçük ekiplere kadar güvenle kullanın, ötesinde gerçek bir mailing list çözümüne geçin.
### Senaryo 4: Catch-all (Tüm Domain'i Tek Adrese Yönlendir)
Domain'iniz altındaki **tüm mailleri** (tanımlı olsun olmasın) tek bir adrese yönlendirmek istiyorsanız catch-all kullanırsınız:
```
# Domain'in tüm gelen maillerini Gmail'e yönlendir
@orneksite.com ornek@gmail.com
```
Yine reload:
```bash
sudo postmap /etc/postfix/virtual
sudo systemctl reload postfix
```
Bu satır, daha önce tanımlanmış spesifik adresler (örn. `info@orneksite.com`, `ben@orneksite.com`) **hariç** kalan tüm gelen mailleri yakalar. Yani spesifik kurallar önceliklidir, catch-all en son devreye girer.
> **Catch-all'ın gerçek bedeli, benim deneyimim:** İlk mail sunucumu kurduğumda heyecanla catch-all'ı açmıştım. "Ne güzel, nereye yazsalar bana gelsin" dedim. **İlk hafta günde 200+ spam aldım.** Botlar domain'leri tarıyor; `admin@`, `webmaster@`, `noreply@`, `john@`, `mary@` gibi rastgele adreslere mail atıyor. Catch-all hepsini kabul ediyor, hepsini Gmail'e yolluyor, Gmail de "kim bu adam, bana bu kadar mail forward'lıyor?" deyip benim IP'mi spam reputation listesine yazıyor.
>
> Sonunda catch-all'ı kapatıp sadece gerçekten kullandığım adresleri tek tek tanımladım. Catch-all'ı şu durumlarda **dikkatli kullanın**: (1) honeypot olarak (spam botlarını yakalamak ve patternleri analiz etmek için ayrı bir hesap), (2) ölmüş bir domain'i kapatmadan önceki "geç sen mailini" geçiş dönemi için. Gerçek üretim trafiği için **kesinlikle önermem.**
> **Spesifik kural + catch-all kombinasyonu:** Aynı dosyada hem spesifik (`info@orneksite.com kullanici1`) hem catch-all (`@orneksite.com ornek@gmail.com`) tanımlıysa Postfix önce spesifik eşleşmeyi arar, bulamazsa catch-all'a düşer. Bu mantığı bilmek catch-all'ı daha kontrollü kullanmanızı sağlar.
### Postmap ve Servis Yenileme: Pratik İpuçları
Her senaryoda iki komutu tekrar tekrar çalıştırdığımızı fark ettiniz: `postmap` ve `systemctl reload`. Bunları her seferinde unutmamak için ben kendime küçük bir alias yaptım:
```bash
# ~/.bashrc veya ~/.zshrc'ye ekle
alias virtual-reload='sudo postmap /etc/postfix/virtual && sudo systemctl reload postfix && echo "virtual map güncellendi"'
```
Artık her değişiklikten sonra `virtual-reload` yazıyorum, gerisi kendiliğinden geliyor.
> **`postmap` neden gerekli?** Postfix `/etc/postfix/virtual` dosyasını **doğrudan okumaz**. Onun ikiz ikincisi olan `/etc/postfix/virtual.db` (Berkeley DB hash dosyası) okunur. `postmap` komutu metin dosyasını parse edip bu binary hash dosyasını üretir. Hash sorgusu O(1) olduğundan, bin satırlık virtual map'te bile lookup süresi sabit kalır. Metin dosyasını değiştirip postmap çalıştırmazsanız Postfix eski kuralları görmeye devam eder, sakın atlamayın!
### Kritik Sorun: SPF/DKIM/DMARC Kırılması
Şimdiye kadar her şey güzel duruyordu, değil mi? `virtual` dosyasına satır ekle, postmap çalıştır, reload at, mail forward olsun. Ama burada büyük bir tuzak var ve bunu görmeden geçmeyeyim, çünkü ilk forward kurulumumda tam buraya takıldım...
İlk Senaryo 1'i kurduktan sonra Hotmail'den `info@orneksite.com`'a test mail attım. Mail Gmail'e geldi ama **spam klasöründe**. "Olamaz" dedim, Show Original'a baktım:
```log-error
Authentication-Results: mx.google.com;
spf=fail (google.com: domain of test@hotmail.com does not designate
1.2.3.4 as permitted sender) smtp.mailfrom=test@hotmail.com;
dmarc=fail (p=QUARANTINE sp=NONE dis=QUARANTINE) header.from=hotmail.com
```
**SPF FAIL ve DMARC FAIL.** Saatlerce nedenini araştırdım, sonunda mantığı çözdüm:
1. Hotmail'deki `test@hotmail.com` bana mail attı. Envelope sender: `test@hotmail.com`.
2. Postfix `info@orneksite.com`'u görüp `virtual`'a baktı, `ornek@gmail.com`'a forward etti.
3. **Forward sırasında envelope sender değişmedi**, hala `test@hotmail.com` görünüyor.
4. Bizim sunucu IP'miz Hotmail'in SPF kaydında **yok** (ki olamaz da, biz Hotmail değiliz).
5. Gmail "bu mail Hotmail'den geliyormuş gibi görünüyor ama Hotmail'in SPF kaydında bu IP yok" diyor: **SPF FAIL**.
6. DMARC `quarantine` politikası devreye giriyor, mail spam'a düşüyor.
Yani forward, doğru kurulmuş bir mail altyapısının SPF/DMARC kontrollerini **doğal olarak kırıyor**. Bu Postfix'in hatası değil, mail standartlarının yapısal bir sonucu.
Çözüm: **SRS (Sender Rewriting Scheme)**.
### SRS Nedir, Ne Yapar?
SRS, forward sırasında envelope sender'ı **rewrite eden** bir mekanizma. `test@hotmail.com` envelope sender'ını alıp şuna çeviriyor:
```
SRS0=hash=tt=hotmail.com=test@orneksite.com
```
Artık envelope sender bizim domain'imiz (`@orneksite.com`). Bizim SPF kaydımız bizim sunucumuzu yetkilendiriyor. SPF PASS, DMARC PASS, mail Inbox'a düşüyor.
> **BONUS:** o garip görünen `SRS0=hash=tt=hotmail.com=test` kısmı, bounce mail'i tekrar `test@hotmail.com`'a yönlendirebilmek için orijinal adresi kodluyor. Hash kısmı da rastgele birinin bizim domain'imizi taklit etmesini önleyen bir doğrulama imzası. Yani sistem hem SPF'i koruyor hem bounce yönlendirmesini bozmuyor.
### postsrsd Kurulumu
Ubuntu'da SRS'i en kolay sağlayan paket `postsrsd`. Postfix ile direkt entegre, TCP üzerinden konuşur, hafiftir:
```bash
sudo nala update
sudo nala install -y postsrsd
```
Servis otomatik başlar ama yine de garantileyin:
```bash
sudo systemctl enable --now postsrsd
sudo systemctl status postsrsd
```
Beklenen çıktı: `active (running)`, dinlenen portlar 10001 (forward) ve 10002 (reverse).
> **NOT:** Rehberde bundan sonra göreceğiniz **10001** ve **10002** portları Ubuntu'daki `postsrsd` paketinin varsayılan değerleri ve ben de bu yazı boyunca aynılarını kullanıyorum. Mecbur değilsiniz; sunucunuzda bu portlar başka bir servis tarafından kullanılıyorsa veya kendi standardınızı koymak isterseniz `/etc/default/postsrsd` üzerinden farklı portlar seçebilirsiniz. Tek koşul: postsrsd config'i ile birazdan göreceğimiz Postfix `main.cf` satırlarındaki port numaralarının **birbiriyle aynı olması**. Yazıda 10001/10002 gördüğünüz her yere kendi seçtiğiniz portları yazmanız yeterli.
### postsrsd Yapılandırması
`/etc/default/postsrsd` dosyasını açın:
```bash
sudo vim /etc/default/postsrsd
```
Şunları ayarlayın:
```bash
# SRS sahibi - bizim ana domain'imiz, rewrite edilen adresler bu domain'den olur
SRS_DOMAIN=orneksite.com
# Rewrite edilmeyecek domain'ler (kendi domainlerimiz)
# Bu domainlerden gelen ve bu domainlere giden mailler dokunulmadan geçer
SRS_EXCLUDE_DOMAINS=orneksite.com,ikincidomain.com
```
Servisi yeniden başlatın:
```bash
sudo systemctl restart postsrsd
```
> **`SRS_EXCLUDE_DOMAINS` neden önemli?** Bu satır olmazsa kendi domain'imizden kendi domain'imize giden mailler de rewrite edilir, bu da gereksiz ve Roundcube/iç teslimat akışını bozar. Buraya yazıyla yazdığınız tüm domain'leri ekleyin (`mydestination`'da olan ne varsa).
### Postfix'i SRS'e Bağlama
`/etc/postfix/main.cf` dosyasının sonuna şu satırları ekleyin:
```ini
# SRS - forward edilen mailler için envelope rewrite (SPF/DMARC korunur)
sender_canonical_maps = tcp:127.0.0.1:10001
sender_canonical_classes = envelope_sender
recipient_canonical_maps = tcp:127.0.0.1:10002
recipient_canonical_classes = envelope_recipient,header_recipient
```
Postfix'i reload edin:
```bash
sudo systemctl reload postfix
```
> **Port 10001 vs 10002, hangisi ne yapıyor?** 10001 portu **forward yönü**: giden mail'in envelope sender'ını rewrite eder (`test@hotmail.com` → `SRS0=...@orneksite.com`). 10002 portu **reverse yönü**: gelen bounce mail'in envelope recipient'ını çözer (`SRS0=...@orneksite.com` → `test@hotmail.com`) ve bounce'u doğru kişiye geri gönderir. İkisi birlikte çalışır, biri eksikse sistem yarım kalır.
### Doğrulama: SRS Çalışıyor mu?
İlk önce postsrsd servisinin port dinlediğinden emin olun:
```bash
sudo ss -tlnp | grep -E '10001|10002'
```
Beklenen çıktı:
```log
LISTEN 0 10 127.0.0.1:10001 0.0.0.0:* users:(("postsrsd",pid=...))
LISTEN 0 10 127.0.0.1:10002 0.0.0.0:* users:(("postsrsd",pid=...))
```
Sonra test mail atın ve mail.log'u izleyin:
```bash
sudo tail -f /var/log/mail.log | grep -E 'SRS|info@orneksite.com'
```
Forward yapılan mail için şu satırları görmeniz gerekir:
```log
postfix/cleanup[...]: ...: replace: header From: test@hotmail.com via ...
postfix/qmgr[...]: ...: from=, ...
postfix/smtp[...]: ...: to=, ..., status=sent
```
`from=` satırı görüyorsanız SRS çalışıyor demektir.
Son test: Gmail'de gelen mail'i açın, sağ üstteki üç noktadan "Show Original" deyin. Şunları arayın:
```log-info
Return-Path:
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of SRS0=...@orneksite.com designates
1.2.3.4 as permitted sender) smtp.mailfrom=...;
dkim=pass header.i=@orneksite.com;
dmarc=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=hotmail.com
```
**SPF PASS, DKIM PASS, DMARC PASS.** Forward artık production-grade.
### Forward Sonrası: Adres Hala Canlı mı? Header'larda Ne Görünür?
Forward kurduktan sonra çoğu insanın aklına gelen ama nadiren açıkça cevaplanan birkaç soru var: "Forward kurduktan sonra `info@orneksite.com` adresi hala canlı mı? Yoksa terk edilmiş bir adres mi oldu?", "Gmail'e ulaşan mailde `info@` adresi hala görünüyor mu, yoksa siliniyor mu?", "Birisi cevap yazarsa cevap nereye gider?"... Hepsini sırayla açayım. Her sorunun başlığına tıklayarak cevabını açıp kapatabilirsiniz:
1) Mail içeriğinde info@ adresi görünür mü?
**Evet, görünür ve değişmez.** Postfix forward yaparken mailin gövdesine ve mevcut header'larına dokunmaz, sadece yeni başlıklar **ekler**. Forward sonrası Gmail'e ulaşan mailin başlık yapısı şöyle olur:
| Header | Değer | Açıklama |
|---|---|---|
| `From:` | `test@hotmail.com` | Orijinal gönderici, değişmez |
| `To:` | `info@orneksite.com` | Orijinal alıcı, değişmez |
| `Subject:` | `[orijinal konu]` | Değişmez |
| `X-Original-To:` | `info@orneksite.com` | Postfix ekler: "asıl alıcı buydu" |
| `Delivered-To:` | `ornek@gmail.com` | Postfix ekler: "buraya teslim ettim" |
Yani Gmail'de mail açıp "Show original" / "Show details" yaptığınızda hala `To: info@orneksite.com` görürsünüz. Gmail kullanıcısı arayüzde "to me" yazısını görür (çünkü `Delivered-To: ornek@gmail.com`) ama detaylarda mailin aslında info@'ya geldiği bellidir.
> **Pratik faydası:** Gmail'de "to:info@orneksite.com" filter'ı kurup forward'lanan tüm mailleri otomatik etiketleyebilir veya ayrı bir klasöre yönlendirebilirsiniz. Bu sayede gelen kutusu karışmaz, hangi mail nereden forward'landı net belli olur.
2) info@ adresi hala "canlı bir adres" olarak kullanılıyor mu?
Bu seçtiğiniz senaryoya göre değişiyor:
| Senaryo | info@ adresinin durumu |
|---|---|
| **Senaryo 1** (sadece harici): `info@ → ornek@gmail.com` | Adres **canlı**, mail kabul ediyor, ama yerel kutuda saklanmıyor. "Transit istasyon" gibi davranır. Roundcube'tan info@ ile giriş yapılamaz (çünkü Linux user/Dovecot kaydı yok) |
| **Senaryo 2** (yerel + harici): `info@ → kullanici1, ornek@gmail.com` | Adres tamamen **canlı**. Mail hem Gmail'e gidiyor hem yerel kutuda saklanıyor. `kullanici1` ile Roundcube'a giriş yapıp info@'ya gelmiş tüm mailleri görebilirsiniz |
| **Senaryo 3** (çoklu harici): `support@ → ali, veli, ayse` | Senaryo 1 gibi transit. Yerel kutu yok, sadece dağıtım |
| **Senaryo 4** (catch-all): `@orneksite.com → ornek@gmail.com` | Senaryo 1 gibi transit. Domain'in tüm adresleri "canlı kabul edilir" gibi gözükür ama hiçbiri yerel saklanmaz |
3) Reply (yanıt) davranışı: cevap nereye gider?
Gmail kullanıcısı forward edilen maile "Reply" derse cevap **orijinal göndericiye** (`test@hotmail.com`) gider, info@'ya değil. Çünkü `From:` header'ı orijinal göndericiyi gösterir ve mail istemcileri reply'da bu header'ı kullanır. Çoğu zaman istenen davranış da budur: "info@ sadece bir kanal, asıl muhatap orijinal gönderici."
Eğer cevabın `info@`'ya gitmesini istiyorsanız iki seçenek var:
- **Senaryo 2 + Roundcube ile cevap:** Yerel kopya zaten `info@`'ya geldiği için, Roundcube'a `kullanici1` ile girip `info@orneksite.com` kimliğiyle reply atarsınız (sender_login_maps yetkisi gerekir, "Yeni Mail Hesabı Ekleme" akışındaki gibi)
- **`Reply-To` header enjeksiyonu (ileri seviye):** Postfix `header_checks` ile gelen mailden önce `Reply-To: info@orneksite.com` başlığı eklersiniz; Gmail "Reply" dediğinde otomatik info@'yu hedef alır. Bu daha karmaşık ve yan etkileri var, basit kullanımlar için önermem.
4) info@'dan mail GÖNDERME tamamen ayrı bir konu
Bu yanlış anlama riski yüksek olduğundan altını çiziyorum: `virtual_alias_maps` **sadece gelen mail trafiğini** yönlendirir. info@'dan mail göndermek için forward kurmuş olmanız yetmez:
- info@ için bir **Linux kullanıcısı** olmalı (`useradd ...`)
- **Dovecot users** dosyasında kaydı olmalı (kimlik doğrulama için)
- **`login_maps.pcre`** dosyasında o kullanıcıya `info@orneksite.com` kimliği yetkisi verilmeli
- Roundcube'a o kimlikle giriş yapılır ve oradan gönderilir
Yani şöyle düşünün: forward "gelen kutuyu boşaltma" işidir, `login_maps` ise "kimliğin gönderme yetkisi"dir. İkisi tamamen ayrı katmanlar. Eğer hem info@'ya gelen mail Gmail'e iletilsin hem de info@'dan mail gönderebilesiniz, **iki şeyi de** yapmanız lazım: önce Yeni Mail Hesabı Ekleme akışıyla info@ hesabını açın, sonra `virtual` dosyasında ek olarak `info@orneksite.com kullanici_info, ornek@gmail.com` yazın (Senaryo 2 pattern'i). Böylece info@ hem alır, hem yerel saklar, hem Gmail'e kopyalar, hem de info@ adından mail gönderebilirsiniz.
### Tuzaklar ve Önemli Notlar
Bu bölümü kapatmadan önce, forward kurarken sıklıkla gözden kaçan ama sonradan baş ağrıtan birkaç noktayı toparlayayım:
> **Mail loop riski:** `info@orneksite.com → ornek@gmail.com` kuruluyken eğer `ornek@gmail.com`'da Gmail filter'ı ile "tüm maileri `info@orneksite.com`'a forward et" kurarsan **sonsuz döngü** olur ve mailler patlar. Postfix `Delivered-To:` header'ı kontrol ederek aynı maili ikinci kez teslim etmeyi reddeder, ama Gmail'in bu kontrolü daha gevşek olabiliyor. Kurduktan sonra bir test mail atıp 2-3 dakika bekleyin: 100 kopya gelmiyorsa loop yok demektir.
> **Spam içerik forward → reputation kaybı:** `info@`'ya gelen spam'i Gmail'e forward ederseniz, Gmail bizim IP'mizi spam göndericisi olarak işaretler. Mail sunucumuzun reputation'ı düşer. Çözüm: forward'dan **önce** SpamAssassin filtresinin geçmesi (zaten kurulu, master.cf'teki `content_filter=spamassassin` satırı bunu yapıyor). Ama yine de zaman zaman `mail-tester.com`'dan reputation skorunu kontrol etmek iyi olur.
> **Gmail'in 1000 mail/gün gönderim limiti:** Eğer `info@orneksite.com`'a günde 500+ mail geliyorsa ve bunların hepsini bir Gmail hesabına forward'larsanız Gmail "bu hesap çok mail alıyor" deyip teslimi yavaşlatır veya keser. Gmail Workspace'in limitleri daha gevşek ama yine de sınırı var. Yoğun trafik için yerel kutu + IMAP istemcisi (Thunderbird, K-9 Mail vb.) daha sağlıklı.
> **`recipient_delimiter = +` ile çakışma:** main.cf'te `recipient_delimiter = +` tanımlı (Adım 5'te gördük). Bu sayede `info+test@orneksite.com` gibi adresler `info@orneksite.com` olarak çözülür ve yine forward edilir. Catch-all kullanıyorsanız bu otomatik olarak doğru çalışır, ama spesifik forward kuralında `info+test@orneksite.com` yazsanız bile lookup `+` öncesini eşleştirir.
> **Backup MX olarak forward'ın güzel kullanımı:** Yedek bir mail sunucusu kurmadan, kritik adresleri (örn. `domain-admin@`, `urgent@`) ikinci bir Gmail/Outlook hesabına forward'lamak basit bir disaster recovery yöntemi. Sunucumuz çökerse o adresler hala (Gmail'in MX'i üzerinden değil, ama gelmişler hala Gmail'de durduğundan) ulaşılabilir kalır. Tabii bizim sunucu MX olarak çözüldüğü için bu otomatik failover değil; sadece "geçmişte gelmiş mailler hala Gmail'de var" garantisi.
> **Forward'ı geri alma:** Bir forward kuralından memnun değilseniz `/etc/postfix/virtual` dosyasından satırı silin (veya `#` ile yorum satırı yapın), `postmap` ve `reload` çalıştırın. Anında devre dışı kalır. Hash binary dosyası satır silindikten sonra `postmap` çalıştırılmazsa eski kuralları gösterebilir, sakın atlamayın.
İşte bu kadar. Forward konusu bitti. Bir sonraki bölümde son sözüme ve önerilerime geçiyoruz...
## Son Sözüm ve Önerilerim
İlk maili gönderip Gmail'de "SPF: PASS, DKIM: PASS, DMARC: PASS" üçlüsünü gördüğümde yaşadığım mutluluğu tarif etmek zor. Hosting'ten sunucuya geçişimden bu yana yıllardır ertelediğim, birkaç defa başlayıp bozduğum, MailCow'la deneyip sevmediğim bu iş - sonunda (binbir emekle de olsa) tamamen çalışır hale geldi. Tamamen benim kontrolümde olan, gizliliğime saygı duyan, hiçbir dev şirkete bağımlı olmayan bir mail sistemimm oldu. :)
**Kazanımlar:**
- **Tam kontrol**: Verileriniz tamamen sizin sunucunuzda. Kimse mailinizi okuyamaz, profilleyemez veya hesabınızı keyfi olarak kapatamaz.
- **Gizlilik**: Reklam şirketlerine satılan profil yok. Google, Microsoft gibi şirketlerin müdahalesinden uzak.
- **Bağımsızlık**: Hosting firmasının mail hizmetine de, Gmail/Hotmail'e de bağımlı değilsiniz.
**Dikkat edilmesi gerekenler:**- **Bakım**: Bu bir "kur ve unut" sistemi değil. Güncellemeleri takip edin, log'ları kontrol edin.
- **Yedekleme**: Düzenli yedekleme stratejiniz olsun. Mail verileri kritik veridir.
- **Monitoring**: Sunucu uptime ve mail kuyruğu izleme sistemleri kurun.
**İlerideki adımlar:**
- DMARC politikasını `quarantine`'den `reject`'e yükseltin (her şey stabil çalıştıktan sonra - kademeli rollout için "Güvenlik Sıkılaştırma" bölümündeki detaylı anlatıma bak)
- Ek domain'ler ekleyin (bu rehberdeki adımları tekrarlayarak)
- Her proje için `no-reply@` hesapları oluşturun ve FROM domain eşleşmesine dikkat edin
- Laravel/web uygulamalarınızı mail sunucunuza bağlayın (yukarıdaki entegrasyon bölümüne bakın)
- Monitoring araçları kurun (uptime kuma, Postfix mail queue alerting vb)
- Düzenli olarak `mail-tester.com` ile puanınızı kontrol edin
- **Google Postmaster Tools'a kayıt olun:** Gmail'e belli bir hacimde mail gönderdiğin anda (günde onlarca+ mail) [postmaster.google.com](https://postmaster.google.com) üzerinden domain'ini doğrula. Oradan IP reputation'ı, spam oranını, authentication istatistiklerini (SPF/DKIM/DMARC pass oranı), teslim edilen mail hacmini görebiliyorsun. Sorun çıktığında kaynağını anlaman için kıymetli - bir gün mailler Gmail'e düşmez olursa ilk bakacağın yer burası olur. Doğrulama DNS TXT kaydı ile yapılıyor, 5 dakikalık iş. Her domain için ayrı ayrı eklemen gerekiyor.
Mail sunucusu kurmak kolay değil(di), doğru. Ben de yıllarca erteledim, birkaç defa sunucuyu bozdum, MailCow ile boğuştum. Ama imkansız da değil. Ve bir kere kurduğunuzda, dijital özgürlüğünüz üzerindeki kontrolü geri aldığınızı hissediyorsunuz. Hosting günlerindeki o hazır mail rahatlığını, bu sefer kendi sunucunuzda, kendi kurallarınızla yaşıyorsunuz. Bu his, harcanan saatlere değer.
## Bonus: Tam Yapılandırma Dosyaları
Referans olması için, tüm yapılandırma dosyalarının tam hallerini topluca ekliyorum. Yukarıdaki adımlarda bir şeyi atladıysanız veya doğrulamak istiyorsanız buraya bakabilirsiniz.
/etc/postfix/main.cf - Postfix Ana Yapılandırma
```ini
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no
append_dot_mydomain = no
readme_directory = no
compatibility_level = 3.6
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.orneksite.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.orneksite.com/privkey.pem
smtpd_tls_security_level = may
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_CAfile = /etc/letsencrypt/live/mail.orneksite.com/cert.pem
smtpd_tls_auth_only = yes
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_relay_restrictions = permit_sasl_authenticated, reject_unauth_destination
myhostname = mail.orneksite.com
mydomain = orneksite.com
myorigin = /etc/mailname
mydestination = $myhostname, $mydomain, ikincidomain.com, mail, localhost.localdomain, localhost, localhost.$mydomain
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.16.0.0/12
mail_name = orneksite.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
home_mailbox = Mail/Inbox/
smtpd_sender_login_maps = pcre:/etc/postfix/login_maps.pcre
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_sender_login_mismatch, reject_unknown_reverse_client_hostname, reject_unknown_sender_domain
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unknown_recipient_domain
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname
header_checks = regexp:/etc/postfix/header_checks
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:12301
non_smtpd_milters = inet:localhost:12301
mailbox_command = /usr/lib/dovecot/deliver
smtpd_forbid_bare_newline = normalize
smtpd_forbid_bare_newline_exclusions = $mynetworks
virtual_alias_maps = hash:/etc/postfix/virtual
# SRS - forward edilen mailler için envelope rewrite (SPF/DMARC korunur)
sender_canonical_maps = tcp:127.0.0.1:10001
sender_canonical_classes = envelope_sender
recipient_canonical_maps = tcp:127.0.0.1:10002
recipient_canonical_classes = envelope_recipient,header_recipient
```
/etc/postfix/master.cf - Postfix Servis Tanımları (özelleştirilmiş kısım)
Dosyanın sonuna eklenen özel servis tanımları:
```
spamassassin unix - n n - - pipe
user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
smtp unix - - n - - smtp
smtp inet n - y - - smtpd
-o content_filter=spamassassin
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_auth_only=yes
-o smtpd_enforce_tls=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_sender_login_maps=pcre:/etc/postfix/login_maps.pcre
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
```
/etc/dovecot/dovecot.conf - Dovecot Tam Yapılandırma
```ini
# --- SSL / TLS bölümü ---
ssl = required
login_trusted_networks = 127.0.0.0/8 172.16.0.0/12
ssl_cert =
/etc/opendkim.conf - OpenDKIM Yapılandırma
```ini
Syslog yes
SyslogSuccess yes
Canonicalization relaxed/simple
Mode sv
OversignHeaders From
UserID opendkim
UMask 007
PidFile /run/opendkim/opendkim.pid
TrustAnchorFile /usr/share/dns/root.key
KeyTable file:/etc/postfix/dkim/keytable
SigningTable refile:/etc/postfix/dkim/signingtable
InternalHosts refile:/etc/postfix/dkim/trustedhosts
Socket inet:12301@localhost
```
docker-compose.yml - Roundcube + PostgreSQL
```yaml
networks:
roundcube-net:
driver: bridge
services:
app-roundcube-db:
image: postgres:16-alpine
container_name: db-postgresql-roundcube
restart: unless-stopped
volumes:
- ./db-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: roundcube
POSTGRES_USER: roundcube
POSTGRES_PASSWORD: BURAYA_GÜÇLÜ_BİR_ŞİFRE_YAZIN
networks:
- roundcube-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U roundcube"]
interval: 10s
timeout: 5s
retries: 5
app-roundcube:
image: roundcube/roundcubemail:latest-apache
container_name: app-roundcube
restart: unless-stopped
depends_on:
app-roundcube-db:
condition: service_healthy
ports:
- "127.0.0.1:8080:80"
volumes:
- ./config:/var/roundcube/config
- ./logs:/var/roundcube/logs
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
ROUNDCUBEMAIL_DB_TYPE: pgsql
ROUNDCUBEMAIL_DB_HOST: app-roundcube-db
ROUNDCUBEMAIL_DB_PORT: "5432"
ROUNDCUBEMAIL_DB_NAME: roundcube
ROUNDCUBEMAIL_DB_USER: roundcube
ROUNDCUBEMAIL_DB_PASSWORD: BURAYA_GÜÇLÜ_BİR_ŞİFRE_YAZIN
ROUNDCUBEMAIL_DEFAULT_HOST: ssl://host.docker.internal
ROUNDCUBEMAIL_DEFAULT_PORT: "993"
ROUNDCUBEMAIL_SMTP_SERVER: ssl://host.docker.internal
ROUNDCUBEMAIL_SMTP_PORT: "465"
ROUNDCUBEMAIL_PLUGINS: archive,zipdownload,managesieve
ROUNDCUBEMAIL_SKIN: elastic
ROUNDCUBEMAIL_ASPELL_DICTS: tr
networks:
- roundcube-net
```
/etc/nginx/sites-available/mail.orneksite.com - Nginx Reverse Proxy
```nginx
server {
listen 80;
listen [::]:80;
server_name mail.orneksite.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mail.orneksite.com;
ssl_certificate /etc/letsencrypt/live/mail.orneksite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mail.orneksite.com/privkey.pem;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
client_max_body_size 25m;
access_log /var/log/nginx/mail.orneksite.com.access.log;
error_log /var/log/nginx/mail.orneksite.com.error.log;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
location ~ /\. { deny all; }
location ~ ^/(config|temp|logs)/ { deny all; }
}
```
/etc/fail2ban/jail.d/ - Fail2ban Jail Dosyaları
**roundcube.conf:**
```ini
[roundcube-auth]
enabled = true
port = http,https
filter = roundcube-auth
logpath = /mnt/HC_Volume_XXXXXXXX/websites/mail.orneksite.com/logs/errors.log
maxretry = 3 # (örnek değer - kendi tercihine göre ayarla)
findtime = 600
bantime = 7200
```
**emailwiz.local:**
```ini
[postfix]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
[postfix-sasl]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
[sieve]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
[dovecot]
enabled = true
ignoreip = 127.0.0.1/8 172.16.0.0/12 192.168.0.0/16
```
Diğer Yardımcı Dosyalar
**/etc/postfix/login_maps.pcre:**
```
/^ben@orneksite\.com$/ ben@orneksite.com
/^info@ikincidomain\.com$/ info@ikincidomain.com
```
**/etc/postfix/header_checks:**
```
/^X-Originating-IP:/ IGNORE
```
> **Dikkat:** `Received` başlıklarını **silmeyin**! Bu, maillerin spam'a düşmesine neden olur. Detaylı açıklama için yukarıdaki "header_checks" bölümüne bakın.
**/etc/postfix/virtual:**
```
ben@orneksite.com kullanici1
info@ikincidomain.com kullanici2
```
Sonra hash'leyin: `sudo postmap /etc/postfix/virtual`
**/etc/postfix/dkim/keytable:**
```
mail._domainkey.orneksite.com orneksite.com:mail:/etc/postfix/dkim/orneksite.com/mail.private
mail._domainkey.ikincidomain.com ikincidomain.com:mail:/etc/postfix/dkim/ikincidomain.com/mail.private
```
**/etc/postfix/dkim/signingtable:**
```
*@orneksite.com mail._domainkey.orneksite.com
*@ikincidomain.com mail._domainkey.ikincidomain.com
```
**/etc/postfix/dkim/trustedhosts:**
```
127.0.0.1
10.1.0.0/16
172.16.0.0/12
```
**/etc/dovecot/users** (şifre hash'lerini kendi kullanıcılarınıza göre düzenleyin):
```
ben@orneksite.com:{CRYPT}$y$j9T$HASH...:UID:GID::/home/kullanici1::userdb_mail=maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
info@ikincidomain.com:{CRYPT}$y$j9T$HASH...:UID:GID::/home/kullanici2::userdb_mail=maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
```
**/mnt/.../config/custom.config.inc.php:**
```php
['verify_peer' => false, 'verify_peer_name' => false],
];
$config['smtp_conn_options'] = [
'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
];
```
---
*Bu rehber, gerçek bir kurulum deneyimine dayanılarak yazılmıştır. Karşılaştığınız sorunlar veya önerileriniz için iletişime geçmekten çekinmeyin.*