SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-22

Gestori di secret self-hosted a confronto su un VPS

Confronto pratico tra OpenBao, Infisical, SOPS con age, credenziali systemd e file env protetto: costi, rischi e scelta adatta a un solo VPS.

Cosa fa un gestore di secret self-hosted che un gestore di password non fa

Un gestore di secret self-hosted fornisce le credenziali ai processi. Un gestore di password fornisce le credenziali alle persone. Tutto il resto deriva da questa differenza. Un gestore di password viene sbloccato da una persona presente e attenta. Un gestore di secret deve fornire alla tua applicazione la password di un database alle 03:00, quando nessuno è sveglio.

Le modalità di errore sono diverse, e questo è importante. Un gestore di password bloccato è un inconveniente: inserisci di nuovo la password principale. Un gestore di secret sigillato causa un'interruzione del servizio: ogni servizio che viene riavviato mentre il gestore è sigillato si avvia senza credenziali e resta inattivo. Usare Vaultwarden come gestore di password personale risolve bene il problema umano. Non risolve il problema delle macchine, e non è mai stato progettato per farlo. Se lo esegui insieme a questa soluzione, gli elementi da proteggere maggiormente sono il suo token amministrativo e il file di backup, non il contenuto del vault, che il client cifra già; una verifica di hardening di Vaultwarden copre entrambi.

Le opzioni realistiche per un unico server rientrano in due gruppi. OpenBao e Infisical sono servizi: un'API, un database, TLS (transport layer security), una procedura di accesso e un processo che devi mantenere in esecuzione. SOPS con age, le credenziali di systemd e i secret di Docker sono file: cifrati a riposo e decifrati da un componente già in esecuzione, senza altri elementi da monitorare.

Ecco subito la risposta più diretta. Per un singolo server usato da una o due persone, le opzioni basate sui file sono generalmente la scelta corretta. Un'istanza OpenBao che nessuno sa disigillare correttamente e per la quale nessuno esegue la rotazione è peggiore di un file di ambiente con permessi mode 600, perché aggiunge un componente soggetto a guasti e un backup che probabilmente configurerai in modo errato, senza offrire alcuna rotazione che non stessi già eseguendo manualmente.

Un file .env con permessi 600 è sufficiente?

Spesso sì. La minaccia che contrasta è che un altro utente del server legga la password del database. I permessi dei file Unix lo impediscono, prima ancora che la rete sia disponibile.

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

Verificatelo da entrambi i lati:

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

Il primo comando stampa il file. Il secondo stampa cat: /etc/myapp/env: Permission denied, perché nobody non appartiene al gruppo myapp e il file non ha permessi per gli altri utenti. Questo è l'intero modello di sicurezza, ed è un modello effettivo.

La fuga di dati avviene subito dopo. Un'unità systemd con EnvironmentFile= copia quei valori nell'ambiente del processo, che è leggibile.

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

Il comando stampa i secret in chiaro, perché /proc/<pid>/environ è leggibile da root e dall'utente con cui viene eseguito il processo. Un sistema di raccolta dei crash che include l'ambiente in un report vede gli stessi dati. Lo stesso vale per qualsiasi strumento eseguito con lo stesso account. Per questo tenere i secret fuori dagli agenti AI inizia dal rimuoverli dall'ambiente. Abbinate il file a un utente di servizio dedicato con privilegi minimi, in modo che «l'utente con cui viene eseguito il processo» non sia root.

SOPS con age: secret crittografati che puoi salvare in git

SOPS (secrets operations) crittografa i valori di un file YAML o JSON e lascia le chiavi in chiaro. age è un piccolo strumento di crittografia che fornisce una sola coppia di chiavi e nessun key server. Insieme consentono di salvare secrets.enc.yaml accanto al codice; git diff mostra comunque quale impostazione è cambiata, senza rivelare a chi legge il nuovo valore.

age è incluso nei pacchetti di Ubuntu 24.04. SOPS invece non lo è, quindi scarica .deb dalla pagina delle release. La versione 3.13.3 era quella corrente ad agosto 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

Genera una coppia di chiavi. age-keygen scrive la chiave privata nel file e stampa la chiave pubblica, quindi vedrai una riga che inizia con 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

Inserisci la chiave pubblica in .sops.yaml nella radice del repository, così non dovrai ricordare il destinatario nella riga di comando.

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

Una regola senza path_regex corrisponde a tutto, ed è ciò che serve inizialmente. Se in seguito ne aggiungi uno, scrivilo in modo che corrisponda al file passato a sops, perché le regole vengono verificate rispetto al percorso di input e non rispetto al file verso cui reindirizzi l'output.

A runtime, passa i valori a un solo processo e a nient'altro:

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

sops exec-env decritta i valori in memoria e li imposta nell'ambiente del processo figlio, quindi nessun testo in chiaro viene scritto su disco. Al processo figlio si applica comunque l'avvertenza sull'ambiente descritta nella sezione precedente.

In questo punto ci sono due problemi frequenti. L'errore Failed to get the data key required to decrypt the SOPS file in systemd significa quasi sempre che SOPS ha cercato nella directory home sbagliata, perché un'unità non eredita il tuo HOME. Imposta esplicitamente il percorso con Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt nell'unità. Inoltre, la modifica di .sops.yaml non ricrittografa ciò che esiste già: l'aggiunta della chiave pubblica di un collega riguarda solo i nuovi file, quindi esegui sops updatekeys secrets.enc.yaml su ogni file esistente. Se la configurazione viene già gestita tramite Ansible, crittografare gli stessi valori con Ansible Vault consente di ottenere lo stesso risultato senza introdurre un secondo strumento.

Credenziali systemd: secret che non raggiungono mai l’ambiente

Ubuntu 24.04 include systemd 255, quindi non è necessaria alcuna installazione. systemd-creds cifra un secret sull’host e systemd lo decifra in una directory privata che solo quel servizio può leggere.

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

Il servizio legge il valore da un file chiamato db_password all’interno della directory indicata da $CREDENTIALS_DIRECTORY. Il valore non è presente nell’ambiente, quindi /proc/<pid>/environ non restituisce informazioni utili e il testo in chiaro non viene mai scritto sul filesystem root.

Verifica che il file venga decifrato prima di indicarlo in una unità:

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

Devi sapere quale chiave lo ha cifrato, perché da questo dipende l’utilità del backup. Per impostazione predefinita, --with-key=auto usa il chip TPM2 (trusted platform module versione 2) quando è presente e utilizzabile; in caso contrario usa la chiave dell’host. La maggior parte delle istanze VPS non dispone di un TPM2.

systemd-analyze has-tpm2

no indica che è stata utilizzata la chiave dell’host. Questa chiave si trova in /var/lib/systemd/credential.secret ed è leggibile solo da root. Se ripristini db_password.cred su una VPS nuova senza quel file, non sarà mai possibile decifrarlo. Includi credential.secret nello stesso backup oppure conserva il testo in chiaro in una posizione che sia ancora accessibile.

Segreti Docker: file in /run/secrets

Compose legge un file dall'host e lo monta nel container in /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

Il primo comando stampa il segreto. Il secondo stampa soltanto DB_PASSWORD_FILE=/run/secrets/db_password, che è il punto: il valore non è mai presente nell'ambiente del container, quindi non compare nell'output di docker inspect. Molte immagini ufficiali prevedono già questo formato e l'immagine Postgres legge POSTGRES_PASSWORD_FILE esattamente in questo modo.

È importante chiarire di cosa si tratta. Al di fuori della modalità Swarm non esiste alcuna cifratura a nessun livello: ./db_password.txt è un file in chiaro sull'host e l'unica protezione consiste nei suoi permessi e nel relativo proprietario. Imposta entrambi manualmente, perché Compose monta senza problemi un file leggibile da tutti. L'analisi più ampia dei compromessi rispetto alla scorciatoia env_file è disponibile nella guida ai file env e ai segreti di Compose.

Quanto costa davvero gestire OpenBao e Vault

OpenBao è il fork di HashiCorp Vault gestito dalla Linux Foundation, avviato dopo che HashiCorp ha applicato a Vault la Business Source License nel 2023. OpenBao resta sotto la MPL 2.0 (Mozilla Public License). La versione 2.6.2 era quella corrente ad agosto 2026. Quasi tutto ciò che segue si applica anche a Vault, perché il fork ha mantenuto la stessa interfaccia dei comandi.

docker pull docker.io/openbao/openbao

I pacchetti per Debian e Ubuntu sono disponibili nella pagina dei download di OpenBao, se preferisci usare apt per gestire gli aggiornamenti. Il server richiede un file di configurazione che definisca un listener e un backend di storage:

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"
}

Poi avvialo una volta:

bao operator init

Per impostazione predefinita, il comando divide la root key in 5 share e ne richiede 3 per effettuare l'unseal; questi sono i flag -key-shares e -key-threshold. Stampa le share e il token root iniziale una sola volta e non li mostra più.

Ora consideriamo l'aspetto che la maggior parte dei confronti tralascia. Un server riavviato è un server sealed. OpenBao mantiene la root key soltanto in memoria. Dopo un riavvio, quindi, non può decrittografare il proprio storage finché qualcuno non fornisce il numero di share richiesto dalla soglia. Un aggiornamento del kernel o una terminazione per esaurimento della memoria lasciano quindi il server sealed e le applicazioni impossibilitate ad autenticarsi.

Su un VPS gestito da una sola persona, la suddivisione Shamir non protegge nulla, perché tutte le cinque share finiscono nello stesso password manager appartenente alla stessa persona. L'auto-unseal sposta la key su un dispositivo o servizio affidabile. In un cloud di grandi dimensioni questo significa in genere usare un servizio gestito di gestione delle chiavi; sul tuo VPS, invece, di solito significa avere un file di chiavi sullo stesso disco dei dati che protegge. Si tratta di una riduzione effettiva della sicurezza, accettata in cambio di un server che torna operativo autonomamente dopo un riavvio. Valuta consapevolmente questo compromesso e annota quale opzione hai scelto.

Infisical: un'interfaccia, un database e una chiave master che devi ancora conservare

Infisical è una piattaforma per la gestione dei secret con interfaccia web, progetti, ambienti e controllo degli accessi per utente. Il self-hosting con Compose è rapido:

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

Modifica .env prima di eseguire l'ultimo comando. Due valori devono essere specifici della tua installazione e uno di questi non deve più cambiare:

openssl rand -hex 16
openssl rand -base64 32

Il primo è ENCRYPTION_KEY, una stringa esadecimale di 16 byte. È la chiave con cui i secret vengono cifrati all'interno di PostgreSQL. Se la perdi, anche un backup integro del database diventa inutilizzabile perché contiene solo ciphertext. Se la cambi su un'istanza in esecuzione, i secret esistenti non possono più essere decifrati. Il secondo è AUTH_SECRET, una stringa base64 di 32 byte utilizzata per le sessioni. SITE_URL deve essere l'URL assoluto che utilizzerai effettivamente per raggiungere il servizio, incluso il protocollo. In caso contrario, il redirect del login non funziona.

Infisical è più adatto di OpenBao quando ti servono principalmente utenti: un'interfaccia web per un piccolo team e la separazione tra ambienti, invece di credenziali del database con scadenza automatica. Richiede PostgreSQL, Redis e un certificato TLS, che dovrai mantenere aggiornati e sottoporre a backup.

Cosa succede quando il servizio dei secret è inattivo e l’applicazione viene riavviata

Questa domanda determina se un servizio per la gestione dei secret può risiedere su un singolo host. I file sono leggibili prima dell’avvio della rete. Un servizio no.

Riavvia l’host: l’applicazione e OpenBao si avviano nello stesso momento. L’applicazione richiede la password del database, OpenBao è ancora sealed, la richiesta fallisce e systemd riavvia l’applicazione in un ciclo continuo finché una persona non inserisce gli unseal shares. Non c’è alcun guasto. Ma non è attivo nulla.

Esistono due modi corretti per gestire il problema. Ordina le unità e consenti all’applicazione di riprovare: After= il servizio per la gestione dei secret, oltre a Restart=on-failure e a un RestartSec= sufficientemente lungo da evitare di sovraccaricare l’API. Oppure recupera il secret durante il deploy anziché all’avvio: scrivilo in un file con modalità 600 oppure in una systemd credential, in modo che il sistema in esecuzione dipenda da un file e non da un’API.

La scadenza dei token è lo stesso problema su una scala temporale più lenta. I token e i lease di OpenBao hanno un time to live, quindi un processo di lunga durata che non esegue mai il rinnovo perde l’accesso in un momento non correlato ad alcun deploy. Il problema è difficile da diagnosticare proprio perché quel giorno non è cambiato nulla.

Backup dello store

Ogni opzione qui descritta ha una chiave, e un backup senza quella chiave è inutilizzabile. Annotate dove si trova la vostra.

Per un file env, il file stesso è il secret, quindi il backup deve essere cifrato. Con SOPS, il file cifrato può essere archiviato in qualsiasi posizione pubblica; la chiave privata age in ~/.config/sops/age/keys.txt è l'elemento che non dovete perdere. Per le credenziali systemd, eseguite il backup di /var/lib/systemd/credential.secret insieme ai file .cred. Per Infisical, create un dump PostgreSQL e archiviate ENCRYPTION_KEY separatamente.

OpenBao con storage raft crea il proprio snapshot:

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

Lo snapshot contiene lo storage cifrato. Per ripristinarlo su un nuovo server servono comunque le unseal share di bao operator init. Un job notturno che copia gli snapshot nello storage a oggetti mentre le share non sono archiviate da nessuna parte non costituisce un backup. Testate il ripristino su una VPS usa e getta prima di fare affidamento su questa procedura.

Registrazione degli audit: chi ha letto quale secret

I file non forniscono una traccia di audit. La modalità e il proprietario indicano chi avrebbe potuto leggere il secret. Non indicano mai chi lo ha fatto. auditd con il monitoraggio del percorso è l’alternativa più vicina, ma segnala che un file è stato aperto, non quale valore è stato utilizzato.

OpenBao registra ogni richiesta su un dispositivo di audit che abiliti esplicitamente:

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

Quella registrazione modifica il modo in cui gestisci il server per due motivi. La maggior parte delle stringhe nelle richieste e nelle risposte viene sottoposta a hashing con HMAC-SHA256 e un salt. Puoi quindi confrontare con il log un valore che già conosci, senza che il log contenga il valore in chiaro. Interi e valori booleani vengono invece scritti in chiaro, quindi un secret numerico non beneficia di questa protezione.

C'è poi un problema operativo importante: OpenBao non risponde alle richieste quando nessun dispositivo di audit abilitato può registrarle. Inoltre, un dispositivo che si blocca fa rimanere le richieste in attesa finché qualcuno non risolve il problema. Un disco pieno su /var/log interrompe di proposito l'API dei secret. Assegna al log di audit uno spazio dedicato e configura una regola logrotate il primo giorno, non dopo il primo outage.

Quale gestore di secret self-hosted conviene usare?

Conta le macchine e conta le persone, quindi scegli.

  1. Una macchina, una persona: usa un file di ambiente con modalità 600, di proprietà di root e leggibile da un utente di servizio. Aggiungi le credenziali di systemd quando vuoi evitare che il valore sia presente nell'ambiente del processo.
  2. Una macchina, da due a cinque persone, configurazione già gestita in git: usa SOPS con age. Ogni persona riceve una coppia di chiavi e .sops.yaml elenca tutte le chiavi pubbliche autorizzate a decrittografare.
  3. Più macchine, un repository di configurazione, nessuna necessità di credenziali con scadenza: usa ancora SOPS con age, con una chiave destinataria per ogni host, in modo che una chiave dell'host sottratta consenta di decrittografare solo i file di quell'host.
  4. Più macchine e più team che hanno effettivamente bisogno di credenziali del database con una durata definita, oltre a un audit trail che qualcuno consulta: usa OpenBao e prevedi nel budget un'ora al mese di lavoro dell'operatore per le esercitazioni di unsealing e ripristino.

La regola alla base di tutti e quattro i casi è la stessa. Esegui il componente più semplice che soddisfa un requisito che puoi enunciare chiaramente, perché un gestore di secret non disponibile è indistinguibile da un gestore di secret vuoto.

FAQ

Vale la pena usare un secrets manager self-hosted su un singolo VPS?

Di solito no, se intendi un servizio come OpenBao o Infisical. Su un solo server usato da una o due persone, un file con variabili d’ambiente impostato a mode 600 o una credenziale cifrata di systemd offre la stessa protezione contro un altro utente locale, senza una procedura di unseal e senza un servizio aggiuntivo da aggiornare. Un secrets manager diventa utile quando hai più macchine e più persone, oppure quando hai una reale necessità di credenziali che scadono senza doverle ruotare manualmente.

Qual è la differenza tra un password manager e un secrets manager?

Un password manager memorizza le credenziali digitate da una persona e viene sbloccato da un utente quando è presente. Un secrets manager fornisce le credenziali ai processi, quindi deve funzionare alle 03:00 senza che nessuno lo controlli. La differenza principale è questa: un password manager bloccato richiede di digitare nuovamente una password principale, mentre un secrets manager sealed interrompe ogni servizio che viene riavviato mentre è sealed.

Cosa accade alle mie applicazioni se OpenBao viene sealed dopo un riavvio?

Non possono recuperare i secret, quindi non si avviano e systemd le riavvia in un ciclo continuo finché qualcuno non fornisce la soglia di unseal, che per impostazione predefinita è 3 di 5 share. OpenBao mantiene la root key soltanto in memoria, quindi ogni riavvio lo porta nuovamente allo stato sealed. Puoi attivare l’auto unseal, accettando che su un singolo VPS la chiave di unseal finisca sullo stesso disco dei dati, oppure generare i secret in un file al momento del deploy, così l’avvio non dipende mai dall’API.

Posso eseguire il commit di file cifrati con SOPS in un repository pubblico?

I valori sono cifrati, quindi sono al sicuro da chiunque non disponga della chiave privata age. Le chiavi non sono cifrate: un lettore può vedere che possiedi STRIPE_SECRET_KEY e SMTP_PASSWORD e con quale frequenza cambia ciascuna di esse. Questi metadati sono accettabili per la maggior parte dei progetti e inaccettabili per alcuni. Mantieni la chiave privata age fuori dal repository ed esegui sops updatekeys su ogni file esistente ogni volta che aggiungi o rimuovi un destinatario.