ГлавнаяБлогHeadscale ile WireGuard'dan Tailscale Mesh'e Geçtim: 60 Günlük Notlarım
Security & Compliance

Headscale ile WireGuard'dan Tailscale Mesh'e Geçtim: 60 Günlük Notlarım

Emre Ferit Aslantaş15 июня 2026 г.13 dkСтатья
headscale tailscale wireguard mesh-vpn homelab networking self-hosted
Sponsored

İki yıldır ev ağımla VPS'lerim arasında çalıştırdığım WireGuard kurulumum vardı. Üç düğümlü: Mac Mini M4 (geliştirme), Proxmox sunucusu (ev içi servisler), Zenth prod VPS'i (Hetzner'da bir kutu). Her şey çalışıyordu — ta ki dördüncü bir cihaz (iPad) ve Proxmox üzerine yeni bir LXC eklemek istediğim güne kadar. O gece üç farklı yerde wg-quick down/up çevirirken kendi kendime "Bu artık ölçeklenmiyor" dedim. Ertesi hafta Headscale + Tailscale client'larına geçtim. Bu yazı o 60 günün dürüst notları: neyin işe yaradığı, hangi hatayla karşılaştığım, ne kadar latency kaybı yaşadığım ve neden Tailscale Cloud'un kendisini seçmedim.

Önce: Üç Düğümlü WireGuard Acısı

WireGuard'ın kendisiyle hiçbir derdim yoktu. wg0.conf dosyaları temizdi, kernel modülü hızlıydı, throughput Gigabit hat üzerinde ~940 Mbps oturuyordu. Sorun "değiştirme" işiydi:

  • Yeni bir peer eklemek için tüm üç düğümdeki config'i güncellemek, public key'i taşımak, AllowedIPs'i tekrar yazmak gerekiyordu.
  • Mac Mini'nin IP'si DHCP üzerinden ev içinde sabit ama Wi-Fi'den Ethernet'e geçtiğinde tunnel bazen sessizce ölüyordu.
  • Zenth VPS'inden ev ağıma geri bağlanırken NAT traversal yoktu — sadece outbound. Yani prod kutusundan eve "ssh" atmak için bir bastion noktasına ihtiyaç vardı.

İki yılda yaklaşık 18-20 kere elle peer ekleyip çıkardığımı git log ile saydım (config'leri ayrı bir özel repo'da tutuyordum). Bu hız, dört cihazın altında bile yorucu. Mesh VPN'in çekici tarafı tam olarak şu: control plane bir kez konuşur, her peer geri kalanını bilir.

Neden Tailscale Cloud Değil, Headscale?

Doğrudan Tailscale'in ücretsiz tier'ı (100 cihaz, kişisel kullanım) aslında benim için fazlasıyla yeterliydi. Sebep teknik değildi: control plane'in başkasında olması. Tailscale ekibi son derece güvenilir, ama:

  1. Zenth VPS prod sistemim para üretiyor; oraya açılan tunnel'ı üçüncü taraf bir "koordinasyon" servisine bağlamak zihinsel olarak rahatsız ediciydi.
  2. Authentik'i SSO için zaten self-host ediyorum; OIDC entegrasyonu Tailscale Cloud yerine Headscale ile çok daha temiz çıkıyor.
  3. Headscale'in lisansı BSD-3, control plane'i Go ile yazılmış tek binary ve SQLite ile başlayabiliyorsunuz. Yani exit stratejisi her zaman elimde.

Trade-off: Tailscale'in mobile app'leri resmi olarak Headscale'i desteklemiyor (login URL override gerekiyor). iOS tarafında ilk hafta çok ağrı çektim, sonunda çalıştı; detay aşağıda.

Kurulum: Proxmox Üzerinde LXC

Headscale control plane'i Proxmox sunucumda yeni bir LXC konteynere koydum (CT106). 512 MB RAM tahsis ettim, 30 günlük p95 kullanım ~110 MB civarında oturdu. CPU bekleme süresi neredeyse sıfır — control plane gerçekten az iş yapıyor.

/etc/headscale/config.yaml dosyamın çekirdek kısmı:

server_url: https://hs.efa.lan
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite

derp:
  server:
    enabled: false
  urls:
    - https://controlplane.tailscale.com/derpmap/default

ip_prefixes:
  - 100.64.0.0/10

dns_config:
  base_domain: efa.lan
  magic_dns: true
  nameservers:
    - 192.168.1.106  # CT100 AdGuard Home

oidc:
  issuer: https://auth.efa.lan/application/o/headscale/
  client_id: headscale
  scope: ["openid", "profile", "email"]
  allowed_groups:
    - homelab-admins

İki önemli karar:

  • Kendi DERP sunucumu çalıştırmadım. Tailscale'in public DERP relay'lerini kullanıyorum çünkü zaten %95 vakada peer-to-peer doğrudan bağlanıyor; relay'e düşen trafiğim son ay loglarına göre toplam ~340 MB. Kendi DERP'ı çalıştırmak ek bir TLS sertifikası, ek bir port forward, ek bir izleme yükü. Henüz değmedi.
  • AdGuard'ı upstream resolver olarak verdim. Böylece Magic DNS hem .efa.lan hem de iç ağdaki cihaz hostname'lerini çözüyor.

Mac Mini, Proxmox host, üç LXC, Zenth VPS ve iPhone — toplam 7 düğüm Headscale'e kayıtlı. Client tarafında standart tailscale up --login-server=https://hs.efa.lan komutu yetiyor; macOS'ta resmi App Store binary'sinde override için sudo tailscale login --login-server=... terminal komutu gerekiyor (App Store sürümü artık tailscale CLI'ı /usr/local/bin'e link'liyor, eskiden değildi).

Karşılaştığım 4 Gerçek Sorun

1. iPhone Resmi App + Headscale: Magic URL Trick

iOS Tailscale uygulaması "Use alternate coordination server" gibi bir ayar göstermiyor. Çözüm açık bir hack: Safari'de https://hs.efa.lan/apple-app-site-association URL'sine bir kez gidip uygulamayı yeniden açmak. Sonra "Sign in" tuşuna bastığınızda uygulama login URL'yi Safari'den hatırlıyor. Bu davranış ne dokümante edilmiş ne de garantili — Tailscale'in bir gün bunu kapatma ihtimali var. Yine de iki ay boyunca çalıştı ve iOS güncellemesi (18.5) sonrasında da bozulmadı.

2. ACL JSON: İlk Konfigde Tüm Trafik Açıktı

Default Headscale ACL'i "action": "accept", "src": ["*"], "dst": ["*:*"] — yani peer'lar birbirine her şeyi açıyor. Bunu fark etmek için bir hafta geçti çünkü "her şey çalışıyor" çoğu zaman "izolasyon yok" demek. Şu anki ACL'im:

{
  "groups": {
    "group:admin": ["emre@efa.lan"],
    "group:prod": ["zenth-vps@efa.lan"],
    "group:home": ["mac-mini@efa.lan", "proxmox@efa.lan"]
  },
  "tagOwners": {
    "tag:prod": ["group:admin"],
    "tag:home-service": ["group:admin"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["group:admin"],
      "dst": ["*:*"]
    },
    {
      "action": "accept",
      "src": ["group:home"],
      "dst": ["group:home:*", "tag:home-service:*"]
    },
    {
      "action": "accept",
      "src": ["group:prod"],
      "dst": ["group:admin:22"]
    }
  ]
}

Mantık: prod kutusu sadece bana SSH atabiliyor (örn. canlı bir incident'ta logları push etmek için), eve doğru başka bir port açamıyor. Ev cihazları kendi aralarında konuşuyor. Sadece ben (Mac Mini + iPhone) her yere ulaşabiliyorum. Yazmak basit görünüyor ama doğru olduğunu 45 satırlık headscale debug nodes test-acl çıktısı ile doğrulamam üç gün sürdü.

3. Subnet Router Drop Olunca Magic DNS Sustu

Proxmox host'unu subnet router olarak ilan ettim (tailscale up --advertise-routes=192.168.1.0/24) — böylece sadece Headscale ağındaki Mac Mini'den AdGuard, Proxmox UI, Uptime Kuma gibi salt-ev IP'lerine direkt ulaşabiliyorum. Bir gün sabah Mac Mini'de hiçbir .lan hostname'i çözülmüyordu. tailscale status baktım; Proxmox offline. LXC içindeki tailscaled servisi sessizce ölmüştü, sebebi: bir gece önceki apt upgrade kernel modülünü güncelledi ama LXC nesting izinleri /dev/net/tun'a izin vermiyordu. LXC config'ine eklemem gereken satır:

# /etc/pve/lxc/106.conf
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file

Bu kararı kayıtlarımda "Headscale-2" etiketiyle saklıyorum çünkü ikinci kez benzer bir kernel upgrade'den sonra Mac Mini'ye userspace-networking flag'i koyarak fallback bağlamayı keşfettim. Önemli ders: subnet router tek nokta arıza. Backup için ikinci bir subnet router (Mac Mini de aynı route'u advertise ediyor, low priority) eklemek istiyorum ama henüz yapmadım.

4. Headscale 0.230.24 Migration: 11 Dakika Downtime

Headscale her minor sürümde DB şemasını değiştirmeyi seviyor. 0.23'ten 0.24'e geçerken pre-auth keys tablosunda silinmiş bir kolon yüzünden migration patladı. Binary üzgün üzgün migration failed diye exit etti. Çözüm: yedek aldığım SQLite dosyasını geri yükleyip önce 0.23.1'e (intermediate) sonra 0.24'e geçiş. Toplam ~11 dakika mesh down. Bu süre boyunca Zenth prod hala çalıştı çünkü web trafiği VPN üzerinden geçmiyor — sadece benim yönetim erişimim koptu. Şimdi her upgrade öncesi cp db.sqlite db.sqlite.$(date +%F) reflex'imde.

Yarım Bıraktığım Şey: Headscale-UI

İlk hafta goharbor-vari bir web UI olan headscale-ui projesini koydum. Niyet: peer listesini görsel takip etmek, pre-auth key'i üretmek. İki gün sonra kaldırdım. Sebep:

  • UI Authentik OIDC ile ek bir konfigürasyon istiyordu (Headscale'in kendisinin OIDC entegrasyonundan ayrı).
  • Read-only kullanım için headscale nodes list ve headscale preauthkeys create -e 1h CLI komutları zaten yeterince hızlıydı; CLI'ın çıktısı insan dostuydu.
  • UI alpha durumunda; bir noktada yanlış bir kullanıcı seçimi ile yanlışlıkla bir node'u expire ettim.

CLI'ya geri döndüm. Bu konuda yarım gün utangaç hissettim — "Self-host eden adam UI'sız mı yaşar?" — ama 60 gün sonra hiç UI özlemedim. Bu da bir öğrenme: her self-hosted ekosistem için ayrı bir admin UI taşımak yerine, tek bir terminal session'ı çoğu zaman daha az teknik borç.

Latency ve Throughput Ölçümleri

Performansı doğrulamak için iki nokta arası iperf3 ve mtr koştum:

| Yol | RTT (eski WireGuard) | RTT (Headscale/Tailscale) | Throughput | |---|---|---|---| | Mac Mini → Proxmox (LAN) | ~0.6 ms | ~0.7 ms | ~935 Mbps | | Mac Mini → Zenth VPS (DE) | ~36 ms | ~37 ms | ~220 Mbps | | iPhone (LTE) → Proxmox | yoktu | ~42 ms | ~85 Mbps | | Zenth VPS → Proxmox | yoktu | ~38 ms | ~210 Mbps |

İlginç gözlemler:

  • LAN içinde WireGuard ile Tailscale arasında pratik olarak fark yok. Tailscale'in kullanıcı alanı wireguard-go'su, Linux kernel WireGuard'ından yaklaşık %2-4 daha yavaş çıktı; bu istatistiksel gürültü seviyesinde.
  • Zenth VPS'e doğru throughput'um 220 Mbps'de takılı çünkü ev fiber bağlantımın upload'u zaten 250/250 Mbps. Yani bottleneck VPN değil.
  • iPhone artık LTE'den eve hostname ile bağlanabiliyor; bu eski setup'ta hiç mümkün değildi.

60 Gün Sonra Notlar

  1. Headscale'i Mart 2026 itibariyle prod-grade saymak güvenli. Resmi 1.0 etiketi yok ama proje 5 yıldır aktif, sürüm notları net, breaking change'ler önceden duyuruluyor. Yine de DB backup zorunlu — kendi acımdan biliyorum.
  2. Tailscale client'ları açık kaynak ve resmi destek almasa da çalışıyor. "Resmi olmayan" terimi pratikte "bug raporlayamazsın" demek. Bu kabul edilebilir bir maliyet.
  3. DERP relay'i kendin host etme tuzağına düşme. İhtiyacın olduğunda kur. Metriklere bak (derp_active_clients) ve karar ver. Benim trafiğimin %95'i doğrudan peer-to-peer akıyor.
  4. ACL'i ilk gün yaz. Açık bir mesh, kapalı bir mesh'ten daha tehlikelidir çünkü "izolasyon var" yanılsaması yaratır.
  5. Mobile app deneyimi hâlâ kırılgan. iOS şu an çalışıyor; bir gün bozulursa, fallback olarak resmi Tailscale Cloud'a geçişin plan B'sini hazır tut. ACL ve user listesini Headscale CLI'dan export edebilmek için haftalık headscale users list -o json > users-$(date +%F).json cron'um var.

WireGuard'ı tamamen söküp atmadım — Zenth prod VPS'i hâlâ bir yedek WireGuard endpoint'i tutuyor, sadece benim ~/.ssh/config'ime giren bir failover yolu. Onu da bir sonraki "kullanmadığım servisleri temizleme" gününde silmeyi planlıyorum, ama acelesi yok. Mesh VPN'in en güzel yan etkisi şu oldu: ev ağıma "uzaktan giriş" diye bir kavram artık yok. Cihaz nerede olursa olsun, aynı ağdaymış gibi davranıyor — ve bu, iki yıldır manuel wg-quick çevirmek için ödediğim psikolojik vergiye değer.

Sponsored

Еженедельная рассылка DevOps

Новые обзоры инструментов, сравнения и тренды DevOps — еженедельно на вашу почту.