Ansible für VPS-Flotten 2026: Playbooks, Rollen und idempotente Server-Setups

Warum Ansible 2026 wieder die erste Wahl für VPS-Flotten ist

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.

Installation und Projektstruktur richtig aufsetzen

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.

Inventar, Gruppen und Variablen-Hierarchie

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.

Fü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.

Das erste idempotente Playbook

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: Wiederverwendbarkeit statt Copy-Paste

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

Ansible Vault und Secrets-Management

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.

Hardening-Playbook: Server in 40 Sekunden absichern

Ein frischer VPS ist offen wie ein Scheunentor. Ein Hardening-Playbook sollte bei jeder Provisionierung laufen und mindestens diese Punkte abdecken:

  1. SSH-Passwort-Auth deaktivieren, nur Key-Auth erlauben
  2. Root-Login verbieten (PermitRootLogin no)
  3. UFW oder nftables aktivieren, nur benötigte Ports öffnen
  4. fail2ban installieren und konfigurieren
  5. Unattended-Upgrades für Security-Patches aktivieren
  6. Zeitzone und NTP setzen
  7. Sysctl-Hardening (IP-Forwarding, SYN-Cookies, rp_filter)

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.

CI/CD-Integration: Playbooks aus der Pipeline

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.

Drift-Erkennung und Monitoring der Flotte

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.

Performance-Tuning für große Flotten

Wenn du von 10 auf 100 Server skalierst, wird Performance zum Thema. Die wichtigsten Hebel mit konkreten Zahlen:

MaßnahmeWirkungAufwand
SSH Pipelining−30 bis −50 % Laufzeit1 Zeile in ansible.cfg
ControlPersist−15 % bei vielen Tasks1 Zeile ssh_args
forks = 20 (statt 5)−60 % bei 50 Hosts1 Zeile
Fact-Caching (Redis)−20 s pro LaufRedis-VPS 4 €
Tags nutzen−70 % bei Teil-LäufenTags in Tasks
strategy: free−25 % bei heterogener Flotte1 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.

Kosten, Anbieter und Betrieb einer 50-Server-Flotte

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):

AnbieterTypSpecsPreis/Monat
HetznerCX222 vCPU, 4 GB, 40 GB NVMe4,35 €
HetznerCPX314 vCPU, 8 GB, 160 GB NVMe15,59 €
NetcupVPS 1000 G114 vCPU, 4 GB, 80 GB SSD6,49 €
ContaboCloud VPS 104 vCPU, 8 GB, 200 GB SSD6,50 €
IONOSVPS Linux XS1 vCPU, 1 GB, 10 GB SSD3,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.

FAQ

Wie viele Server kann ich mit einem Ansible-Control-Node verwalten?

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).

Ansible oder Terraform – was brauche ich für VPS?

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.

Wie sichere ich Secrets in Ansible richtig ab?

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.

Warum zeigt mein Playbook bei jedem Lauf „changed“?

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.

Lohnt sich Ansible Automation Platform 2026?

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.