
Tailscale vs. Wireguard 2026 – Mesh-VPN im Vergleich
Tailscale vs. WireGuard 2026 im Vergleich: Mesh-VPN, Performance, Preise, Headscale, Setup-Befehle und klare Empfehlung für Admins, DevOps und Gamer.
Zwischen 2023 und 2026 hat sich die Landschaft der Konfigurationsmanagement-Tools deutlich verschoben. Terraform und OpenTofu dominieren die Provisionierung, Kubernetes hat sich im Container-Bereich durchgesetzt – und trotzdem bleibt Ansible für klassische VPS-Flotten das pragmatischste Werkzeug. Der Grund ist simpel: Die meisten Workloads laufen nicht in Kubernetes. Sie laufen auf einzelnen VPS mit nginx, PostgreSQL, Docker, WireGuard und einem halben Dutzend Cronjobs.
Ansible 2.17 (Core) bzw. das ansible-core 2.19-Paket aus dem Frühjahr 2026 arbeitet agentless über SSH. Kein Node-Exporter, kein Daemon, kein Zertifikatsroulette. Ein Control-Node, ein Inventar, ein Playbook – fertig. Für Flotten zwischen 3 und 300 Servern ist das der Sweet Spot.
Die Zahlen sprechen für sich: Ein typischer Setup-Lauf auf einem frischen Ubuntu 24.04 VPS (2 vCPU, 4 GB RAM) dauert mit einem gut strukturierten Playbook 38 bis 55 Sekunden. Bei 50 Servern parallel mit forks = 20 liegt die Gesamtlaufzeit bei unter vier Minuten. Zum Vergleich: Ein manuell durchgeklicktes Setup dauert pro Server 20–40 Minuten.
Dieser Guide zeigt dir den kompletten Weg von der Installation über Rollen, Vault und CI/CD bis zum Drift-Monitoring – mit konkreten Befehlen, Zahlen und Preisangaben für 2026.
Ansible läuft auf dem Control-Node, nicht auf den Zielservern. Ein 2-vCPU-VPS mit 2 GB RAM reicht für Flotten bis etwa 200 Hosts. Bei Hetzner kostet ein CX22 mit 2 vCPU und 4 GB RAM aktuell 4,35 €/Monat, bei Netcup ein VPS 1000 G11 für 6,49 €/Monat. Für 50 Zielserver reicht das dicke.
Installation via pipx ist 2026 der sauberste Weg, weil du damit problemlos mehrere Ansible-Versionen parallel betreiben kannst:
apt update && apt install -y python3-pip pipx sshpass
pipx install --include-deps ansible
pipx inject ansible ansible-lint yamllint
ansible --version
# ansible [core 2.19.1]
# python version = 3.12.3
Die Projektstruktur sollte von Anfang an sauber sein. Ein flaches Verzeichnis mit drei Playbooks wird nach sechs Monaten unwartbar. Bewährt hat sich diese Struktur:
infra/
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ ├── all.yml
│ │ ├── webservers.yml
│ │ └── dbservers.yml
│ └── staging/
├── roles/
│ ├── common/
│ ├── nginx/
│ ├── postgres/
│ └── firewall/
├── playbooks/
│ ├── site.yml
│ ├── hardening.yml
│ └── update.yml
└── requirements.yml
Die ansible.cfg im Projektroot überschreibt die globale Konfiguration und sollte mindestens diese Werte enthalten:
[defaults]
inventory = inventory/production
roles_path = roles
collections_path = collections
host_key_checking = False
forks = 20
stdout_callback = yaml
callbacks_enabled = timer, profile_tasks
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
Pipelining ist der wichtigste Performance-Hebel überhaupt. Es reduziert die Anzahl der SSH-Verbindungen pro Task drastisch und beschleunigt Playbooks um 30–50 %. Voraussetzung: requiretty darf in der sudoers-Konfiguration nicht gesetzt sein – bei Ubuntu und Debian ist das seit Jahren Standard.
Ein YAML-Inventar ist 2026 Pflicht. INI funktioniert noch, ist aber bei verschachtelten Gruppen und Host-Variablen unlesbar. Für eine typische Flotte sieht das so aus:
all:
children:
webservers:
hosts:
web01.example.com:
web02.example.com:
web03.example.com:
vars:
nginx_worker_processes: 4
dbservers:
hosts:
db01.example.com:
vars:
postgres_max_connections: 300
monitoring:
hosts:
mon01.example.com:
vars:
ansible_user: deploy
ansible_port: 22
ansible_python_interpreter: /usr/bin/python3
timezone: Europe/Berlin
Die Variablen-Hierarchie ist der Punkt, an dem die meisten Einsteiger scheitern. Ansible löst Variablen in einer festen Reihenfolge auf: role defaults < inventory group_vars/all < inventory group_vars/<group> < host_vars < play vars < role vars < set_fact. Merksatz: Je spezifischer, desto höher die Priorität.
roles/x/defaults/main.ymlFür Secrets nutzt du group_vars/all/vault.yml – dazu später mehr. Wichtig: Lege niemals Klartext-Passwörter in host_vars, auch nicht „nur zum Testen“. Git vergisst nichts.
Idempotenz bedeutet: Ein Playbook kann beliebig oft laufen und führt zum gleichen Endzustand. Beim ersten Lauf ändert es Dinge (changed), beim zweiten Lauf nicht mehr (ok). Das ist der Kern von Ansible und der Grund, warum du keine Shell-Skripte mehr schreiben willst.
Ein typisches Basis-Playbook für einen Webserver:
---
- name: Webserver konfigurieren
hosts: webservers
become: true
roles:
- common
- firewall
- nginx
tasks:
- name: nginx läuft und ist enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Deploy-Verzeichnis existiert
ansible.builtin.file:
path: /var/www/app
state: directory
owner: www-data
group: www-data
mode: "0755"
Der entscheidende Unterschied zu Shell: Du beschreibst einen Zustand, keine Abfolge von Befehlen. state: directory ist idempotent – existiert das Verzeichnis schon mit den richtigen Rechten, passiert nichts.
Was du vermeiden solltest: ansible.builtin.shell oder command ohne creates/removes-Guard. Diese Tasks sind nie idempotent und tauchen bei jedem Lauf als changed auf. Wenn es nicht anders geht, nutze:
- name: Datenbank initialisieren
ansible.builtin.command: /usr/local/bin/db-init
args:
creates: /var/lib/app/.initialized
Prüfe Idempotenz immer mit einem Doppellauf. Der zweite Lauf muss changed=0 zeigen:
ansible-playbook playbooks/site.yml
ansible-playbook playbooks/site.yml | grep -E "changed=[0-9]+"
# web01.example.com : ok=14 changed=0 unreachable=0 failed=0
Rollen sind das Rückgrat jeder ernsthaften Ansible-Struktur. Eine Rolle kapselt Tasks, Handler, Templates, Files und Defaults für genau eine Aufgabe. Statt nginx-Konfiguration in fünf Playbooks zu duplizieren, schreibst du sie einmal.
Eine Rolle erzeugst du mit:
ansible-galaxy role init roles/nginx --init-path roles
Für eine produktive nginx-Rolle sieht die Struktur so aus:
roles/nginx/
├── defaults/main.yml # nginx_worker_processes: 2
├── handlers/main.yml # restart nginx
├── tasks/main.yml
├── templates/
│ ├── nginx.conf.j2
│ └── vhost.conf.j2
├── files/
│ └── ssl-params.conf
└── meta/main.yml
Der Handler ist der wichtigste Teil. Statt nginx nach jedem Task neu zu starten, feuert der Handler nur, wenn sich wirklich etwas geändert hat – und zwar genau einmal am Ende des Plays:
# handlers/main.yml
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
# tasks/main.yml
- name: nginx Konfiguration deployen
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
validate: "nginx -t -c %s"
notify: restart nginx
Das validate-Argument ist Gold wert: Ansible schreibt die Datei in ein Temp-Verzeichnis, führt nginx -t darauf aus und kopiert sie nur bei Erfolg an den Zielort. Eine kaputte Konfiguration kann so nie live gehen.
Für Community-Rollen nutzt du requirements.yml und installierst alles reproduzierbar:
# requirements.yml
roles:
- name: geerlingguy.docker
version: 7.4.1
- name: geerlingguy.postgresql
version: 3.5.0
collections:
- name: community.general
version: ">=9.0.0"
- name: ansible.posix
version: 1.5.4
ansible-galaxy install -r requirements.yml --force
Passwörter, API-Keys und TLS-Private-Keys gehören nicht in Git – außer verschlüsselt. Ansible Vault macht genau das. Ein Vault-Passwort schützt eine Datei, die du bedenkenlos committen kannst.
ansible-vault create group_vars/all/vault.yml
ansible-vault edit group_vars/all/vault.yml
ansible-vault encrypt_string 'SuperGeheim123' --name 'db_password'
Die bewährte Trennung: vault.yml enthält die echten Werte mit Präfix vault_, und vars.yml mappt sie auf die Variablennamen, die die Rollen erwarten:
# group_vars/all/vault.yml (verschlüsselt)
vault_db_password: "SuperGeheim123"
vault_api_token: "ghp_xxxxxxxxxxxx"
# group_vars/all/vars.yml (Klartext)
db_password: "{{ vault_db_password }}"
api_token: "{{ vault_api_token }}"
Für automatisierte Läufe (CI/CD) gibst du das Passwort per Datei oder Umgebungsvariable mit:
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# oder
export ANSIBLE_VAULT_PASSWORD_FILE=/run/secrets/vault_pass
Seit Ansible 2.16 gibt es zusätzlich ansible-vault encrypt_string --vault-id für Multi-Vault-Setups. In größeren Teams hat sich bewährt: ein Vault-Passwort pro Umgebung (production, staging), jeweils im Passwortmanager, nicht im Repo. Bei 5–10 Admins kostet ein 1Password-Business-Tarif 7,99 €/User/Monat – günstiger als ein einziger Vorfall.
Ein frischer VPS ist offen wie ein Scheunentor. Ein Hardening-Playbook sollte bei jeder Provisionierung laufen und mindestens diese Punkte abdecken:
PermitRootLogin no)Die SSH-Härtung in einer Rolle sieht so aus:
- name: SSH-Konfiguration härten
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
validate: "/usr/sbin/sshd -t -f %s"
loop:
- { regexp: '^#?PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^#?PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^#?PubkeyAuthentication', line: 'PubkeyAuthentication yes' }
- { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' }
- { regexp: '^#?MaxAuthTries', line: 'MaxAuthTries 3' }
notify: restart sshd
Für die Firewall mit ufw reicht ein kompakter Block:
- name: UFW Standard-Policy
community.general.ufw:
direction: incoming
policy: deny
- name: Erlaubte Ports
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: [22, 80, 443]
- name: UFW aktivieren
community.general.ufw:
state: enabled
Ein vollständiger Hardening-Lauf auf einem frischen Ubuntu 24.04 dauert im Schnitt 42 Sekunden und reduziert die offenen Ports von durchschnittlich 4 auf 2. Bei einem Server, der 6,99 €/Monat kostet, ist das die günstigste Versicherung, die du kaufen kannst.
Ansible gehört in die Pipeline, nicht auf den Laptop des Admins. Der typische Flow: Push auf main → Lint → Dry-Run gegen Staging → Apply gegen Production. Bei GitLab CI sieht der Job so aus:
stages: [lint, deploy]
lint:
stage: lint
image: python:3.12-slim
before_script:
- pip install ansible-core==2.19.1 ansible-lint yamllint
script:
- yamllint -c .yamllint .
- ansible-lint playbooks/
deploy:
stage: deploy
image: python:3.12-slim
before_script:
- pip install ansible-core==2.19.1
- mkdir -p ~/.ssh && echo "$SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$VAULT_PASS" > /tmp/vault_pass
script:
- ansible-playbook playbooks/site.yml
--check --diff --limit staging
- ansible-playbook playbooks/site.yml --limit production
--vault-password-file /tmp/vault_pass
only:
- main
--check --diff ist der Dry-Run: Ansible zeigt, was sich ändern würde, ohne es zu tun. Achtung: Nicht alle Module unterstützen Check-Mode vollständig (insbesondere command und shell). Für kritische Rollen lohnt sich ein separates Staging-Inventar mit identischer Konfiguration.
Für die Secrets in der Pipeline gilt: SSH-Key und Vault-Passwort kommen aus CI-Variablen, niemals aus dem Repo. GitLab CI bietet masked variables, GitHub Actions encrypted secrets. Ein GitHub-Team-Account kostet 2026 3,80 €/User/Monat, GitLab Premium 26 €/User/Monat – für kleine Teams reicht GitLab Free mit Self-Hosted-Runner auf einem 4,35-€-VPS.
Idempotente Playbooks sind nur dann wertvoll, wenn du regelmäßig prüfst, ob die Realität noch dem Soll entspricht. Server „driften“ – jemand installiert manuell ein Paket, ändert eine Config, öffnet einen Port. Das nennt man Configuration Drift.
Der einfachste Drift-Check ist ein täglicher Check-Mode-Lauf per Cron auf dem Control-Node:
# /etc/cron.d/ansible-drift
0 4 * * * deploy /usr/local/bin/ansible-playbook \
/opt/infra/playbooks/site.yml \
--check --diff \
--vault-password-file /run/secrets/vault_pass \
>> /var/log/ansible-drift.log 2>&1
Die Log-Ausgabe wertest du mit einem simplen grep auf changed=[1-9]. Alles, was dort auftaucht, ist Drift und gehört in ein Ticket. Für Flotten ab 30 Servern lohnt sich ein zentrales Dashboard – entweder via Prometheus-Textfile-Exporter oder mit einem kleinen Python-Skript, das die changed-Zahlen pro Host in eine InfluxDB schreibt.
Ein weiterer wichtiger Punkt: Fakten-Caching. Bei 100 Hosts dauert das Setup-Gathering allein 15–25 Sekunden. Mit aktiviertem Cache (Redis) reduzierst du das auf unter 2 Sekunden:
[defaults]
gathering = smart
fact_caching = redis
fact_caching_connection = localhost:6379:0
fact_caching_timeout = 86400
Ein Redis-VPS mit 1 GB RAM kostet bei den meisten Anbietern 3,50–5 €/Monat – bei Flotten ab 50 Hosts amortisiert sich das nach der ersten Woche.
Wenn du von 10 auf 100 Server skalierst, wird Performance zum Thema. Die wichtigsten Hebel mit konkreten Zahlen:
| Maßnahme | Wirkung | Aufwand |
|---|---|---|
| SSH Pipelining | −30 bis −50 % Laufzeit | 1 Zeile in ansible.cfg |
| ControlPersist | −15 % bei vielen Tasks | 1 Zeile ssh_args |
forks = 20 (statt 5) | −60 % bei 50 Hosts | 1 Zeile |
| Fact-Caching (Redis) | −20 s pro Lauf | Redis-VPS 4 € |
| Tags nutzen | −70 % bei Teil-Läufen | Tags in Tasks |
strategy: free | −25 % bei heterogener Flotte | 1 Zeile pro Play |
| Mitmproxy/SSH-Multiplexing | −10 % | Konfiguration |
Tags sind der unterschätzteste Hebel. Wenn du nur nginx-Konfiguration ändern willst, willst du nicht 40 andere Tasks laufen lassen:
- name: nginx-Konfiguration deployen
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags: [nginx, config]
ansible-playbook site.yml --tags nginx
# Laufzeit: 8 s statt 52 s bei 20 Hosts
Die free-Strategie lässt Hosts unabhängig voneinander durchlaufen, statt auf den langsamsten zu warten. Bei heterogenen Flotten (manche Hosts brauchen 30 s, andere 90 s) sparst du damit im Schnitt 25 % Gesamtlaufzeit. Nachteil: Handler feuern pro Host, nicht global – bei Rolling-Deployments musst du das berücksichtigen.
Was kostet eine Ansible-verwaltete VPS-Flotte 2026 konkret? Hier ein realistisches Beispiel mit 50 Servern (30 Web, 10 App, 5 DB, 3 Monitoring, 2 Control/CI):
| Anbieter | Typ | Specs | Preis/Monat |
|---|---|---|---|
| Hetzner | CX22 | 2 vCPU, 4 GB, 40 GB NVMe | 4,35 € |
| Hetzner | CPX31 | 4 vCPU, 8 GB, 160 GB NVMe | 15,59 € |
| Netcup | VPS 1000 G11 | 4 vCPU, 4 GB, 80 GB SSD | 6,49 € |
| Contabo | Cloud VPS 10 | 4 vCPU, 8 GB, 200 GB SSD | 6,50 € |
| IONOS | VPS Linux XS | 1 vCPU, 1 GB, 10 GB SSD | 3,00 € |
Für 50 Server à 4,35 € (Hetzner CX22) landest du bei 217,50 €/Monat plus Control-Node (4,35 €) plus Redis (4 €) plus Backup-Speicher (Hetzner Storage Box BX11 mit 1 TB für 3,81 €). Macht rund 230 €/Monat für die komplette Flotte.
Zum Vergleich: Ein einzelner dedizierter Server mit vergleichbarer Leistung (50 × 2 vCPU = 100 vCPU, 200 GB RAM) kostet bei Hetzner als AX52 (8 vCPU, 64 GB) schon 47 €/Monat – für die gleiche Rechenleistung bräuchtest du aber drei bis vier davon. Die VPS-Flotte ist bei horizontal skalierbaren Workloads fast immer günstiger und ausfallsicherer.
Der eigentliche Kostenhebel ist aber die Arbeitszeit. Ein manueller Server-Setup kostet bei 60 €/h Stundensatz etwa 30 € pro Server. Bei 50 Servern sind das 1.500 € – einmalig. Mit Ansible: 4 Stunden Setup der Rollen (240 €) plus 3 Minuten pro weiterem Server. Ab dem 10. Server rechnet sich Ansible immer.
Ein VPS mit 2 vCPU und 4 GB RAM schafft problemlos 200–300 Hosts, wenn du Pipelining und Fact-Caching aktivierst. Der Flaschenhals ist nicht die CPU, sondern die Anzahl gleichzeitiger SSH-Verbindungen. Mit forks = 50 und ControlPersist sind 300 Hosts in 6–8 Minuten durchgelaufen. Für mehr brauchst du mehrere Control-Nodes oder Ansible Automation Platform (ab 12.000 €/Jahr).
Beides, aber für unterschiedliche Dinge. Terraform (oder OpenTofu) provisioniert die Server selbst – bei Hetzner Cloud, Netcup oder IONOS per API. Ansible konfiguriert danach das Betriebssystem und die Anwendungen. Für reine VPS-Flotten ohne Cloud-API reicht Ansible allein, weil du die Server manuell bestellst. Sobald du automatisch skalierst, brauchst du Terraform plus Ansible.
Nutze Ansible Vault für alles Sensible, committe nur verschlüsselte Dateien und halte das Vault-Passwort außerhalb des Repos (Passwortmanager, CI-Variable, HashiCorp Vault). Für größere Teams empfiehlt sich ein externer Secret-Store wie HashiCorp Vault oder Bitwarden Secrets Manager, aus dem Ansible per community.hashi_vault-Collection liest. Das Vault-Passwort selbst rotierst du mindestens jährlich.
Fast immer liegt es an shell- oder command-Tasks ohne creates/removes-Guard, oder an Templates mit variablen Zeitstempeln. Prüfe mit --diff, welche Datei sich ändert. Ein häufiger Klassiker: lineinfile ohne korrektes regexp fügt die Zeile jedes Mal erneut an. Nutze ansible-lint – es erkennt die meisten Idempotenz-Probleme automatisch.
Für die meisten VPS-Flotten nicht. Die Platform kostet ab etwa 12.000 €/Jahr für 100 Nodes und bietet Web-UI, RBAC, Audit-Log und Support. Wenn du unter 200 Server verwaltest und ein Team von 2–5 Admins hast, reicht Ansible Core plus GitLab CI plus ein selbstgebautes AWX-Setup auf einem 15-€-VPS. AWX ist die Open-Source-Variante und deckt 80 % der Platform-Features ab.