SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor

Self-hosted secrets manager vergelijking voor VPS

Welke secrets manager past op uw VPS? Wij vergelijken OpenBao, Infisical, SOPS met age en systemd credentials. Ontdek de operationele kosten en risico's voor uw server.

Wat een self-hosted secrets manager doet wat een wachtwoordmanager niet doet

Een self-hosted secrets manager verstrekt inloggegevens aan processen. Een wachtwoordmanager verstrekt inloggegevens aan personen. Al het overige vloeit voort uit dat ene verschil. Een wachtwoordmanager wordt ontgrendeld door een mens die aanwezig is en oplet. Een secrets manager moet uw applicatie om 03:00 uur van een databasewachtwoord voorzien wanneer er niemand wakker is.

De faalwijzen verschillen op een manier die ertoe doet. Een vergrendelde wachtwoordmanager is een ongemak: u voert het hoofdwachtwoord opnieuw in. Een verzegelde secrets manager is een storing: elke service die herstart terwijl deze verzegeld is, komt op zonder inloggegevens en blijft offline. Vaultwarden draaien als uw eigen wachtwoordmanager lost het menselijke probleem goed op. Het lost het machineprobleem niet op, en daarvoor is het ook nooit gebouwd.

De realistische opties voor één server vallen in twee groepen uiteen. OpenBao en Infisical zijn services: een API, een database, TLS (transport layer security), een inlogstap en een proces dat u nu draaiende moet houden. SOPS met age, systemd credentials en Docker secrets zijn bestanden: versleuteld op schijf, ontsleuteld door iets dat al draait, zonder extra zaken om te monitoren.

Hier is het eerlijke antwoord vooraf. Voor een enkele machine met één of twee gebruikers zijn de bestandsgebaseerde opties meestal de juiste keuze. Een OpenBao die niemand correct ontzegelt en niemand roteert, is slechter dan een env-bestand met mode 600, omdat het een bewegend onderdeel en een back-up toevoegt die u waarschijnlijk verkeerd zult beheren, terwijl het geen rotatie oplevert die u niet al handmatig uitvoerde.

Is een env-bestand met modus 600 voldoende?

Vaak wel. De dreiging waartegen dit beschermt, is een andere gebruiker op de server die uw databasewachtwoord leest. Unix-bestandsrechten doen dat, en ze doen dat nog voordat het netwerk actief is.

sudo install -d -m 750 -o root -g myapp /etc/myapp
sudo install -m 640 -o root -g myapp /dev/null /etc/myapp/env
sudoedit /etc/myapp/env

Controleer dit vanaf beide kanten:

sudo -u myapp cat /etc/myapp/env
sudo -u nobody cat /etc/myapp/env

Het eerste commando toont de inhoud van het bestand. Het tweede commando toont cat: /etc/myapp/env: Permission denied, omdat nobody geen deel uitmaakt van de myapp-groep en het bestand geen rechten voor 'world' bevat. Dat is het volledige beveiligingsmodel, en het is een reëel model.

Het lek ontstaat in de volgende stap. Een systemd-unit met EnvironmentFile= kopieert deze waarden naar de procesomgeving, en de procesomgeving is leesbaar.

[Service]
User=myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/local/bin/myapp
sudo cat /proc/$(pgrep -n myapp)/environ | tr '\0' '\n'

Dit toont uw geheimen in leesbare tekst, omdat /proc/<pid>/environ leesbaar is voor root en voor de gebruiker waaronder het proces draait. Een crash-reporter die de omgeving aan een rapport toevoegt, ziet hetzelfde. Dat geldt ook voor elk hulpprogramma dat onder hetzelfde account draait; daarom begint het buiten de AI-agents houden van geheimen met het verwijderen ervan uit de omgeving. Combineer het bestand met een toegewezen servicegebruiker met lage rechten, zodat "de gebruiker waaronder het proces draait" niet root is.

SOPS met age: versleutelde geheimen die u kunt committen naar git

SOPS (secrets operations) versleutelt de waarden in een YAML- of JSON-bestand en laat de sleutels in leesbare tekst staan. age is een compacte versleutelingstool die werkt met één sleutelpaar en geen sleutelserver vereist. Samen stellen ze u in staat om secrets.enc.yaml naast uw code te committen, waarbij git diff nog steeds aangeeft welke instelling is gewijzigd zonder dat een lezer ziet naar welke waarde deze is aangepast.

age is beschikbaar in de pakketbronnen van Ubuntu 24.04. SOPS niet, dus download de .deb van de releasepagina. Versie 3.13.3 was actueel in augustus 2026.

sudo apt update && sudo apt install -y age
curl -LO https://github.com/getsops/sops/releases/download/v3.13.3/sops_3.13.3_amd64.deb
sudo apt install -y ./sops_3.13.3_amd64.deb
sops --version

Genereer een sleutelpaar. age-keygen schrijft de privésleutel naar het bestand en toont de publieke sleutel, waardoor u een regel ziet die begint met Public key: age1....

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
age-keygen -y ~/.config/sops/age/keys.txt

Plaats de publieke sleutel in .sops.yaml in de hoofdmap van de repository, zodat u de ontvanger nooit op de opdrachtregel hoeft op te geven.

creation_rules:
  - age: age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23c
sops encrypt secrets.yaml > secrets.enc.yaml
sops decrypt secrets.enc.yaml

Een regel zonder path_regex komt met alles overeen; dit is wat u in het begin wilt. Als u later een regel toevoegt, zorg dan dat deze overeenkomt met het bestand dat u doorgeeft aan sops, omdat regels worden gecontroleerd op basis van het invoerpad en niet op basis van het bestand waarnaar u de uitvoer omleidt.

Geef tijdens runtime de waarden door aan één proces en niets anders:

sops exec-env secrets.enc.yaml './myapp'

sops exec-env ontsleutelt in het geheugen en stelt de waarden in de omgeving van het onderliggende proces in, waardoor er geen leesbare tekst naar de schijf wordt geschreven. De waarschuwing over de omgeving uit de vorige sectie is nog steeds van toepassing op dat proces.

Twee zaken zorgen hier vaak voor problemen. De foutmelding Failed to get the data key required to decrypt the SOPS file onder systemd betekent bijna altijd dat SOPS in de verkeerde homedirectory zocht, omdat een unit uw HOME niet overerft. Stel het pad expliciet in met Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt in de unit. Daarnaast geldt dat het bewerken van .sops.yaml niets opnieuw versleutelt wat al bestaat: het toevoegen van de publieke sleutel van een collega heeft alleen invloed op nieuwe bestanden, dus voer sops updatekeys secrets.enc.yaml uit op elk bestaand bestand. Als uw configuratie al via Ansible verloopt, bereikt u met het versleutelen van dezelfde waarden met Ansible Vault hetzelfde resultaat zonder een tweede tool.

systemd credentials: geheimen die de omgeving nooit bereiken

Ubuntu 24.04 wordt geleverd met systemd 255, dus installatie is niet nodig. systemd-creds versleutelt een geheim op de host en systemd ontsleutelt dit naar een privémap die alleen door de betreffende service kan worden gelezen.

sudo systemd-creds setup
sudo install -d -m 700 /etc/myapp
echo -n 'hunter2' | sudo systemd-creds encrypt --name=db_password - /etc/myapp/db_password.cred
[Service]
User=myapp
LoadCredentialEncrypted=db_password:/etc/myapp/db_password.cred
ExecStart=/usr/local/bin/myapp

De service leest de waarde uit een bestand genaamd db_password in de map die wordt aangeduid door $CREDENTIALS_DIRECTORY. De waarde staat niet in de omgeving, dus /proc/<pid>/environ toont niets nuttigs en de platte tekst komt nooit op het root-bestandssysteem terecht.

Controleer of het bestand ontsleutelt voordat u een unit ernaar verwijst:

sudo systemd-creds decrypt /etc/myapp/db_password.cred -

Weet welke sleutel het heeft versleuteld, want dat bepaalt of uw back-up bruikbaar is. De standaard --with-key=auto gebruikt de TPM2 (trusted platform module version 2)-chip wanneer deze aanwezig en bruikbaar is, en anders de hostsleutel. De meeste VPS-instanties hebben geen TPM2.

systemd-analyze has-tpm2

no betekent dat de hostsleutel is gebruikt en die sleutel bevindt zich in /var/lib/systemd/credential.secret, alleen leesbaar door root. Herstel db_password.cred naar een nieuwe VPS zonder dat bestand en niets zal het ooit ontsleutelen. Kopieer credential.secret naar dezelfde back-up of bewaar de platte tekst op een plek die u nog kunt bereiken.

Docker secrets: bestanden onder /run/secrets

Compose leest een bestand van de host en koppelt dit in de container op /run/secrets/<name>.

services:
  app:
    image: myapp:latest
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt
docker compose exec app cat /run/secrets/db_password
docker compose exec app env | grep -i password

Het eerste commando toont het geheim. Het tweede commando toont alleen DB_PASSWORD_FILE=/run/secrets/db_password, en dat is het doel: de waarde staat nooit in de omgevingsvariabelen van de container, waardoor deze niet zichtbaar is in de uitvoer van docker inspect. Veel officiële images verwachten deze structuur al, en de Postgres image leest POSTGRES_PASSWORD_FILE precies op deze manier.

Wees duidelijk over wat dit is. Buiten Swarm-modus is er op geen enkele laag sprake van versleuteling: ./db_password.txt is een plat tekstbestand op de host, en de enige bescherming is de bestandsmodus en de eigenaar. Stel deze zelf in, want Compose koppelt zonder waarschuwing een bestand dat voor iedereen leesbaar is. De bredere afwegingen ten opzichte van de eenvoudige env_file-snelkoppeling staan in de handleiding voor Compose env-bestanden en secrets.

Wat OpenBao en Vault werkelijk kosten om te draaien

OpenBao is de Linux Foundation-fork van HashiCorp Vault, gestart nadat HashiCorp in 2023 de licentie van Vault wijzigde naar de Business Source License. OpenBao blijft onder de MPL 2.0 (Mozilla Public License) vallen. Release 2.6.2 was actueel in augustus 2026. Bijna alles hieronder is ook van toepassing op Vault, omdat de fork hetzelfde commando-oppervlak heeft behouden.

docker pull docker.io/openbao/openbao

Debian- en Ubuntu-pakketten staan op de OpenBao-downloadpagina als u liever heeft dat apt de upgrades beheert. De server heeft een configuratiebestand nodig met een listener en een storage backend:

listener "tcp" {
  address       = "127.0.0.1:8200"
  tls_cert_file = "/path/to/full-chain.pem"
  tls_key_file  = "/path/to/private-key.pem"
}

storage "raft" {
  path = "/path/to/raft/data"
  node_id = "raft_node_1"
}

Start het vervolgens eenmalig:

bao operator init

Standaard splitst dit de root key in 5 shares en vereist het er 3 om te unsealen; dit zijn de -key-shares en -key-threshold flags. Het toont de shares en de initiële root token één keer en daarna nooit meer.

Nu het deel dat de meeste vergelijkingen overslaan. Een herstarte server is een sealed server. OpenBao houdt de root key alleen in het geheugen, dus na een herstart kan het zijn eigen opslag niet ontsleutelen totdat iemand het vereiste aantal shares aanlevert. Een kernel-update of een out of memory kill resulteert daarom in een sealed server en applicaties die niet kunnen inloggen.

Op een VPS voor één persoon beschermt de Shamir-split niets, omdat alle vijf de shares in dezelfde wachtwoordmanager van dezelfde persoon belanden. Auto unseal verplaatst de sleutel naar een vertrouwd apparaat of een vertrouwde dienst; in een grote cloudomgeving betekent dit een beheerde sleuteldienst, en op uw VPS betekent dit meestal een sleutelbestand op dezelfde schijf als de data die het beschermt. Dat is een reële vermindering van de beveiliging, ingeruild voor een server die na een herstart uit zichzelf weer opkomt. Maak deze afweging bewust en noteer welke keuze u heeft gemaakt.

Infisical: een UI, een database en een hoofdsleutel die u zelf beheert

Infisical is een platform voor geheimen met een webinterface, projecten, omgevingen en toegangscontrole per gebruiker. Het zelf hosten via Compose is eenvoudig:

curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
docker compose -f docker-compose.prod.yml up -d

Bewerk .env vóór het laatste commando. Twee waarden moeten door u worden ingevuld, en één daarvan mag daarna nooit meer worden gewijzigd:

openssl rand -hex 16
openssl rand -base64 32

De eerste is ENCRYPTION_KEY, een hexadecimale string van 16 bytes. Dit is de sleutel waarmee uw geheimen binnen PostgreSQL worden versleuteld. Als u deze verliest, is een perfecte databaseback-up niets meer dan onleesbare ciphertext, en het wijzigen ervan op een draaiende instantie zorgt ervoor dat bestaande geheimen niet meer kunnen worden ontsleuteld. De tweede is AUTH_SECRET, een base64-string van 32 bytes die wordt gebruikt voor sessies. SITE_URL moet de absolute URL zijn die u daadwerkelijk gaat gebruiken, inclusief protocol, anders werkt de redirect na het inloggen niet.

Infisical is een betere keuze dan OpenBao wanneer u vooral behoefte heeft aan menselijke interactie: een webinterface voor een klein team en scheiding tussen omgevingen, in plaats van database-inloggegevens die automatisch verlopen. Het vereist dat u PostgreSQL, Redis en een TLS-certificaat beheert, die u vanaf nu zelf moet patchen en back-uppen.

Wat gebeurt er als de secrets-service offline is en uw applicatie herstart

Deze vraag bepaalt of een secrets-service thuishoort op een enkele server. Bestanden zijn leesbaar voordat het netwerk start. Een service is dat niet.

Herstart de server en uw applicatie en OpenBao starten op hetzelfde moment. De applicatie vraagt om het databasewachtwoord, OpenBao is nog steeds vergrendeld (sealed), het verzoek mislukt en systemd herstart de applicatie in een lus totdat een beheerder handmatig de unseal-sleutels invoert. Er is niets defect. Er is echter ook niets operationeel.

Er zijn twee eerlijke manieren om dit af te handelen. Definieer de volgorde van de units en laat de applicatie opnieuw proberen: After= de secrets-service, plus Restart=on-failure en een RestartSec= die lang genoeg is zodat u de API niet overbelast. Of haal het geheim op tijdens de uitrol (deploy) in plaats van bij het opstarten: schrijf het geheim naar een bestand met modus 600 of een systemd credential, zodat het draaiende systeem afhankelijk is van een bestand in plaats van een API.

Het verlopen van tokens is hetzelfde probleem op een langzamere tijdschaal. OpenBao-tokens en leases hebben een time-to-live, waardoor een langlopend proces dat nooit vernieuwt de toegang verliest op een moment dat losstaat van enige uitrol. Die fout is verwarrend, juist omdat er die dag niets is veranderd.

Back-ups maken van de opslag zelf

Elke optie hier heeft een sleutel; een back-up zonder die sleutel is waardeloos. Noteer waar de uwe zich bevindt.

Bij een env-bestand is het bestand zelf het geheim, dus de back-up moet versleuteld zijn. Bij SOPS kan het versleutelde bestand overal openbaar worden opgeslagen, maar de age private key op ~/.config/sops/age/keys.txt is hetgeen u niet mag verliezen. Maak voor systemd credentials een back-up van /var/lib/systemd/credential.secret samen met de .cred-bestanden. Maak voor Infisical een PostgreSQL-dump en sla ENCRYPTION_KEY ergens anders op.

OpenBao met raft-opslag maakt zijn eigen snapshot:

bao operator raft snapshot save backup.snap
bao operator raft snapshot restore backup.snap

De snapshot bevat uw versleutelde opslag, dus voor het herstellen naar een nieuwe server zijn nog steeds de unseal shares van bao operator init vereist. Een dagelijkse taak die snapshots naar object storage kopieert terwijl de shares nergens worden opgeslagen, is geen back-up. Test het herstel op een tijdelijke VPS voordat u ervan afhankelijk bent.

Audit logging: wie heeft welk geheim gelezen

Bestanden bieden geen audit-spoor. De modus en de eigenaar geven aan wie het geheim zou kunnen lezen. Ze vertellen nooit wie dat daadwerkelijk heeft gedaan. auditd met een watch op het pad is het dichtstbijzijnde alternatief, en dit rapporteert dat een bestand is geopend, niet welke waarde er is gebruikt.

OpenBao logt elk verzoek naar een audit-apparaat dat u expliciet inschakelt:

bao audit enable file file_path=/var/log/openbao_audit.log

Twee feiten over dit logboek veranderen de manier waarop u de server beheert. De meeste strings in verzoeken en antwoorden worden gehasht met HMAC-SHA256 en een salt, zodat u een waarde die u al kent kunt matchen met het logboek zonder dat het logboek zelf de platte tekst bevat. Integers en booleans worden in leesbare vorm weggeschreven, waardoor een numeriek geheim geen bescherming geniet door deze hashing.

Dan de operationele valkuil: OpenBao reageert niet op verzoeken wanneer er geen ingeschakeld audit-apparaat is om deze vast te leggen, en een apparaat dat op een blokkerende manier faalt, zorgt ervoor dat verzoeken blijven hangen totdat iemand het probleem oplost. Een volle schijf op /var/log legt uw secrets API plat, zoals ontworpen. Geef het audit-logboek vanaf de eerste dag een eigen schijfruimte en een logrotate-regel, niet pas na de eerste storing.

Welke self-hosted secrets manager moet u gebruiken?

Tel het aantal machines en het aantal personen, en maak dan uw keuze.

  1. Eén machine, één persoon: een env-bestand met modus 600, eigendom van root en leesbaar voor een service-gebruiker. Voeg systemd-credentials toe wanneer u de waarde uit de procesomgeving wilt halen.
  2. Eén machine, twee tot vijf personen, configuratie al in git: SOPS met age. Elke persoon krijgt een sleutelpaar en .sops.yaml bevat een lijst met alle publieke sleutels die mogen ontsleutelen.
  3. Meerdere machines, één configuratie-repository, geen behoefte aan credentials die verlopen: nog steeds SOPS met age, met één ontvangersleutel per host, zodat een gestolen hostsleutel alleen de bestanden van die specifieke host ontsleutelt.
  4. Meerdere machines en meerdere teams die daadwerkelijk database-credentials met een beperkte levensduur nodig hebben, plus een audit-trail die door iemand wordt gecontroleerd: OpenBao, en reserveer een uur per maand aan beheertijd voor het unsealing-proces en het oefenen van restores.

De regel die aan alle vier ten grondslag ligt, is hetzelfde. Gebruik de kleinst mogelijke oplossing die voldoet aan een vereiste die u hardop kunt uitspreken, want een secrets manager die offline is, is niet te onderscheiden van een secrets manager die leeg is.

FAQ

Is een self-hosted secrets manager de moeite waard voor één VPS?

Meestal niet, als u doelt op een dienst zoals OpenBao of Infisical. Op één server met één of twee gebruikers bieden een env-bestand met modus 600 of een versleutelde systemd-credential dezelfde bescherming tegen andere lokale gebruikers, zonder dat er een unseal-stap nodig is of een extra dienst die gepatcht moet worden. Een secrets-dienst wordt pas rendabel zodra u over meerdere machines en meerdere gebruikers beschikt, of wanneer u daadwerkelijk behoefte heeft aan credentials die verlopen zonder dat iemand ze handmatig hoeft te roteren.

Wat is het verschil tussen een wachtwoordmanager en een secrets manager?

Een wachtwoordmanager slaat credentials op die een persoon intypt, en een mens ontgrendelt deze terwijl hij aanwezig is. Een secrets manager verstrekt credentials aan processen; deze moet dus om 03:00 uur kunnen werken zonder toezicht. Het gevolg hiervan vormt het onderscheid: een vergrendelde wachtwoordmanager dwingt u tot het opnieuw intypen van een hoofdwachtwoord, terwijl een vergrendelde (sealed) secrets manager elke service stopt die herstart terwijl de manager vergrendeld is.

Wat gebeurt er met mijn applicaties als OpenBao na een reboot vergrendeld is?

Ze kunnen hun secrets niet ophalen en starten daarom niet op. systemd herstart ze vervolgens in een lus totdat iemand de unseal-drempel levert, wat standaard 3 van de 5 shares is. OpenBao houdt de root key alleen in het geheugen, dus elke herstart vergrendelt de dienst opnieuw. Schakel auto unseal in, waarbij u accepteert dat op een enkele VPS de unseal key op dezelfde schijf belandt als de data, of schrijf secrets tijdens de deploy naar een bestand zodat het opstartproces nooit afhankelijk is van de API.

Kan ik met SOPS versleutelde bestanden naar een publieke repository pushen?

De waarden zijn versleuteld en dus veilig voor iedereen die niet over de age private key beschikt. De sleutels zijn niet versleuteld: een lezer kan zien dat u over STRIPE_SECRET_KEY en SMTP_PASSWORD beschikt, en hoe vaak elk daarvan wijzigt. Die metadata is voor de meeste projecten acceptabel, maar voor sommige niet. Houd de age private key buiten de repository en voer sops updatekeys uit op elk bestaand bestand telkens wanneer u een ontvanger toevoegt of verwijdert.