LLM self-hosted su VPS Hetzner con Ollama: deployment in produzione per PMI con vincoli di data sovereignty
Il 14 gennaio 2026 ho preso una decisione nella mia sandbox di ricerca applicata: smettere di fare esclusivamente benchmark di confronto tra Claude e modelli frontier via API e iniziare a usare un LLM self-hosted come strumento di lavoro reale per le mansioni ripetitive dove il costo token diventa significativo. Server scelto: Hetzner AX102 a Falkenstein (Ryzen 9 7950X3D, 128 GB DDR5 ECC, 2 NVMe da 2 TB in RAID 1, un'unica GPU NVIDIA RTX 6000 Ada Generation con 48 GB di VRAM). Canone mensile: 369€ netti, datacenter tedesco ISO 27001 certificato, nessun dato che esce dall'UE. Stack: Ubuntu 24.04 LTS, Docker 26, Ollama 0.6 con quantizzazioni GGUF, reverse proxy Caddy con mTLS, firewall nftables, VPN WireGuard per l'accesso amministrativo. Lavoro che il server esegue oggi: classificazione di 4.500 documenti PDF scansionati al giorno, generazione di abstract di articoli di ricerca in lingua italiana, embedding per un indice vettoriale pgvector su una knowledge base tecnica interna di 280 MB. Costo variabile di inferenza: zero per token. Costo fisso totale: canone server + 45€/mese di RAM aggiuntiva Anthropic Claude Sonnet usata per task che richiedono il frontier model, per un confronto onesto dentro lo stesso workflow.
Dopo 93 giorni di esercizio posso confrontare in modo strutturato quattro percorsi che vedo proposti alle PMI italiane come "soluzioni AI": managed API pura (Claude/OpenAI), ibrido cloud con provider europeo, self-hosted VPS con GPU dedicata come il mio setup, infrastruttura dedicata on-premise. Non sono tutti equivalenti e non sono tutti adatti agli stessi casi d'uso. In questo articolo li metto in tabella con i parametri che contano per un decision maker PMI - costo fisso e variabile, latenza, giurisdizione, rischio operativo, tempo di implementazione - e ti spiego quando ciascuno vince e quando perde. Parto dal contesto che giustifica la scelta, passo al confronto operativo, chiudo con i dettagli del mio setup reale su Hetzner.
Perché parliamo di self-hosted nel 2026 e non nel 2023
Nel 2023 il ragionamento tecnico prevalente era "i modelli open-weights non reggono il confronto con GPT-4, meglio pagare i token". Nel 2026 quel ragionamento è obsoleto su almeno tre dimensioni. La prima è la qualità dei modelli: Llama 3.3 70B, Mistral Large 2.1, Qwen 2.5 72B ottengono punteggi confrontabili con GPT-4 Turbo su task di dominio ristretto (classificazione, estrazione entità, riassunto italiano, generazione strutturata). Non sostituiscono Claude Sonnet 4.5 nei task di ragionamento complesso multi-step, ma coprono il 70-80% dei casi d'uso enterprise reali. La seconda dimensione è il costo hardware: una GPU NVIDIA RTX 6000 Ada con 48 GB di VRAM, sufficiente per far girare Llama 3.3 70B quantizzato a 4 bit in modo fluido, costa oggi in hosting dedicato tra i 250€ e i 400€/mese a seconda del provider europeo. Due anni fa quella stessa configurazione era il doppio. Nel mio confronto tra GPU cloud inference LLM self-hosted per PMI ho valutato quattro provider alternativi (Scaleway, Lambda Labs, RunPod, Hetzner) con parametri di costo, giurisdizione e tempi di provisioning. La terza dimensione, quella che pesa di più per il decisore italiano, è regolatoria.
Il report Deloitte State of AI in the Enterprise 2026 presentato a Davos il 21 gennaio 2026 su un campione di 3.235 leader in 24 paesi riporta che l'83% delle aziende enterprise considera sovereign AI strategico. Significa: dati sotto giurisdizione nazionale o europea, vendor valutati per country of origin, controllo diretto sulla supply chain AI. L'AI Act europeo entra in piena applicazione il 2 agosto 2026, con obblighi stringenti di trasparenza e tracciabilità. La direttiva NIS2, obbligatoria in Italia dal 17 ottobre 2024 per le categorie essenziali e importanti, impone requisiti specifici di gestione della supply chain IT che includono anche i fornitori di servizi cloud. Per una PMI italiana che rientra nel perimetro NIS2 - e sono molte più di quelle che lo sanno - avere un registrar .it che invia i payload dei ticket a un'API negli Stati Uniti è un rischio di compliance concreto.
Se questo tema ti interessa dal punto di vista architetturale e non solo regolatorio, nel mio hub dedicato all'integrazione AI trovi articoli su RAG privato, MCP server custom che collegano gestionali esistenti a modelli self-hosted, e pattern di deployment per stack ibridi PHP+Python dove ciascun linguaggio fa quello che fa meglio.
Quattro percorsi di deployment a confronto
Ecco la tabella che uso nei miei assessment quando aiuto una PMI a scegliere il percorso AI più adatto. I costi sono stimati su un carico mensile di 50 milioni di token elaborati complessivi (input + output), che corrisponde a un'integrazione AI seria su un'azienda da 30-80 dipendenti con workflow parzialmente automatizzati.
| Criterio | Managed API pura | Ibrido cloud EU | Self-hosted VPS GPU | On-premise dedicato |
|---|---|---|---|---|
| Costo fisso/mese | 0€ | 80-200€ (Azure EU) | 250-450€ (Hetzner/OVH GPU) | 1500-3500€ (ammortamento + rack) |
| Costo variabile/50M token | 400-900€ (Claude/GPT) | 300-700€ | 0€ (dopo il canone) | 0€ (dopo l'infrastruttura) |
| Totale stimato/mese | 400-900€ | 380-900€ | 250-450€ | 1500-3500€ |
| Latenza p50 | 300-600ms | 250-500ms | 80-250ms (rete locale) | 20-100ms (LAN) |
| Giurisdizione dati | USA / multipla | UE (contratto dedicato) | UE (server) | Nazionale |
| Data sovereignty | Garantita via contratto | Garantita via contratto EU | Totale (nessun dato esce) | Totale |
| Tempo implementazione | 1-3 giorni | 1-2 settimane | 2-4 settimane | 8-16 settimane |
| Competenze richieste | Dev backend | Dev + cloud architect | Dev + DevOps + GPU | Dev + DevOps + ops fisico |
| Qualità modello top | Frontier (GPT-5, Claude Opus) | Frontier via Azure | Llama 3.3 70B, Mistral Large | Stesso self-hosted |
| Rischio operativo | Basso (vendor gestisce) | Basso-medio | Medio (tu gestisci GPU) | Alto (tu gestisci tutto) |
| ROI break-even vs API | N/A | 12-18 mesi | 4-8 mesi | 24-36 mesi |
Un paio di letture strategiche sulla tabella. Il self-hosted VPS con GPU dedicata è il percorso con il miglior rapporto tra sovranità, costo totale e tempo di implementazione per PMI che hanno già un minimo di competenze DevOps interne o un consulente senior che gestisce l'infrastruttura. L'on-premise dedicato ha senso solo per aziende con vincoli regolatori speciali (difesa, sanità, bancario con deroghe) oppure con volumi tali da ammortizzare il CapEx iniziale. La managed API pura resta la scelta razionale per pilot brevi, sperimentazione, e task che richiedono frontier model sui quali il 70-80% dei modelli open-weights ancora non arriva.
Il mio setup Hetzner in dettaglio operativo
Il server che descrivo sotto è quello che gira oggi nella mia sandbox di ricerca. Non è né l'unica configurazione possibile né la migliore in assoluto: è una configurazione che funziona, replicabile da un consulente senior in 3-5 giornate di lavoro incluse security hardening e testing.
Dimensionamento hardware
L'AX102 di Hetzner combina Ryzen 9 7950X3D (16 core, 32 thread, 128 MB L3 cache grazie al 3D V-Cache), 128 GB di RAM DDR5 ECC, e un bay PCIe 4.0 x16 in cui ho installato una NVIDIA RTX 6000 Ada Generation con 48 GB di VRAM GDDR6. La scelta della RTX 6000 Ada e non della più economica A6000 è deliberata: la memoria unificata di Ada consente di caricare Llama 3.3 70B in Q4_K_M (circa 42 GB) con ancora 6 GB liberi per KV cache e batching, impossibile con i 48 GB della A6000 in modalità real-world workload. Quantizzazioni più spinte (Q3_K, IQ2) entrerebbero comunque nella A6000 ma con degrado di qualità misurabile su task di classificazione italiana: nel mio benchmark interno, la Q4_K_M mantiene una F1 del 92% contro il riferimento Claude Sonnet, la Q3_K scende al 87%, la IQ2 crolla al 81%.
Non tutti i casi d'uso richiedono modelli da 70B. Se il tuo workload è classificazione, estrazione, NER italiano, riassunto breve, un Mistral Small 3 (24B) quantizzato Q4_K_M occupa 14 GB di VRAM e gira su una GPU da 24 GB come la RTX 4090 o RTX A4000, abbattendo il canone server a 190-250€/mese. Il dimensionamento va fatto sul carico reale, non sull'ambizione.
Rete, sicurezza, isolamento
Il VPS Hetzner espone di default un IP pubblico IPv4 e IPv6. La prima cosa che ho fatto, subito dopo il provisioning, è stata chiudere praticamente tutto: nftables con policy default drop, solo SSH su porta custom esposto al mondo (dietro Fail2ban con regole adattive), tutto il resto raggiungibile esclusivamente via VPN WireGuard. La tunnel WireGuard termina sul VPS e serve sia per l'accesso amministrativo mio (client Linux) sia per le applicazioni backend che consumano Ollama - che NON parlano con il VPS via internet pubblico, ma via rete privata WireGuard. Significa che anche se un attaccante scoprisse l'IP del VPS e trovasse un 0day su Ollama (improbabile ma non impossibile), la superficie di attacco sarebbe ridotta alla sola porta SSH hardened.
Ollama gira dentro un container Docker con --network=host per performance GPU, ma il daemon Ollama è bindato esplicitamente su 127.0.0.1:11434 via OLLAMA_HOST=127.0.0.1. Caddy funge da reverse proxy TLS davanti, ma ascolta solo sull'interfaccia WireGuard 10.77.0.1, non sull'interfaccia pubblica. Le applicazioni client (backend PHP Laravel su altri VPS in vari datacenter) si connettono al peer WireGuard 10.77.0.1:443 con certificato client mTLS obbligatorio. Anche se il token API di Ollama venisse esfiltrato - cosa che tecnicamente non esiste in Ollama standard, ma uso un layer di autenticazione aggiuntivo - senza certificato client la connessione viene chiusa al livello TLS.
Integrazione con backend PHP
Il codice applicativo Laravel che consuma l'LLM è scritto come se stesse parlando con qualunque API HTTP: niente accoppiamento a Ollama, solo un'interfaccia LlmClientContract con implementazioni swappabili (OllamaClient, ClaudeClient, OpenAIClient). Questo permette di routare dinamicamente un singolo task al modello più economico che soddisfa i requisiti di qualità. Il pattern è MCP-compatible: sto lavorando per migrare la pipeline a un vero MCP server che esporre la selezione modello come tool registrato, coerente con lo standard donato da Anthropic alla Linux Foundation il 9 dicembre 2025 e che oggi conta 10.000+ server pubblici attivi.
interface LlmClientContract
{
public function complete(string $prompt, array $options = []): LlmResponse;
public function embed(string $text): EmbeddingVector;
}
final class OllamaClient implements LlmClientContract
{
public function __construct(
private readonly HttpClient $http,
private readonly string $model = 'llama3.3:70b-instruct-q4_K_M',
private readonly string $endpoint = 'https://llm-01.internal:443'
) {}
public function complete(string $prompt, array $options = []): LlmResponse
{
$start = hrtime(true);
$response = $this->http
->withOptions(['verify' => '/etc/ssl/mtls/ca.crt'])
->withCert('/etc/ssl/mtls/client.crt', '/etc/ssl/mtls/client.key')
->timeout(120)
->post("{$this->endpoint}/api/generate", [
'model' => $this->model,
'prompt' => $prompt,
'stream' => false,
'options' => array_merge(['temperature' => 0.2], $options),
'keep_alive' => '30m',
])
->throw()
->json();
return new LlmResponse(
content: $response['response'],
latencyMs: (hrtime(true) - $start) / 1_000_000,
tokenCount: $response['eval_count'] ?? 0,
model: $this->model,
provider: 'ollama_self_hosted'
);
}
}Il parametro keep_alive di 30 minuti è importante: istruisce Ollama a tenere il modello caricato in VRAM per 30 minuti dopo l'ultima chiamata. Senza keep-alive, un modello 70B richiede 8-12 secondi di cold-start ogni invocazione, con keep-alive la seconda chiamata risponde in 150-250ms.
Monitoraggio, quote, failover
Prometheus scrapa ollama-exporter ogni 10 secondi: latency p50/p95/p99, VRAM usage, GPU utilization, temperatura GPU, token/sec throughput. Grafana dashboard custom con alert Telegram: se la latency p95 supera i 3 secondi per più di 5 minuti, scatta un ping. Se la VRAM supera il 92% (sintomo di modello duplicato caricato accidentalmente via parallel requests su modelli diversi), scatta un ping più aggressivo. Il failover verso Claude API è automatico: il mio LlmRouter ha un health check ogni 60 secondi verso /api/tags di Ollama, se tre check consecutivi falliscono ruota il traffico su Claude Sonnet 4.5 fino al ripristino. I pattern di resilienza e logging delle decisioni AI sono quelli che descrivo nel mio articolo su integrazione LLM nella pipeline CI/CD senza creare debito tecnico. Nei 93 giorni di esercizio questo failover è scattato due volte: una per un aggiornamento Ollama andato male in fase di test (problema mio), una per un reboot Hetzner programmato notificato 48h prima.
OWASP LLM Top 10 applicata al deployment self-hosted
Self-hosted non significa automaticamente più sicuro. L'OWASP LLM Top 10 2025 ha rilevanze specifiche per chi opera il proprio modello, diverse dal caso managed API. LLM02 - Sensitive Information Disclosure cambia forma: non hai più il rischio di esfiltrazione verso il training dei vendor, ma hai il rischio che il modello memorizzi informazioni sensibili nei suoi log o nelle cache e le restituisca in risposte successive. Mitigazione: log-level=warn minimo, rotazione log, nessuna persistenza del prompt su disk. LLM03 - Supply Chain: i modelli GGUF che scarichi da Hugging Face non sono sempre quello che dichiarano di essere. Uso esclusivamente repository ufficiali firmati, verifico SHA-256 contro i manifesti pubblicati dai maintainer originali, non scarico mai modelli "fine-tuned community" senza audit della provenienza. LLM04 - Data and Model Poisoning: se in futuro farò fine-tuning sul mio dataset interno, il rischio di backdoor entra nel mio perimetro, non più nel perimetro del vendor. LLM10 - Unbounded Consumption è il più sottovalutato: senza rate limiting applicativo, un singolo workflow broken che manda 10.000 richieste al minuto non ti fa fatturare 2.000€ in token (come su API), ma ti porta la GPU al thermal throttling e ti fa crashare il servizio. Rate limit via nginx-module-ratelimit su ogni endpoint, budget quotidiano per singolo API consumer.
Il caso PMI reale e il modello di decisione che applico
Quando una PMI italiana mi contatta per un assessment AI, il primo parametro che considero non è il modello top: è la sensibilità dei dati e i vincoli regolatori applicabili. Se l'azienda processa dati personali sensibili ex art. 9 GDPR (sanitari, giudiziari, biometrici), oppure rientra nel perimetro NIS2 categoria essenziale, oppure ha clausole contrattuali B2B che vietano il cloud extra-UE, il self-hosted è la risposta di default salvo giustificazioni tecniche forti in contrario. Il 9% di grandi imprese italiane che secondo l'Osservatorio Artificial Intelligence del PoliMI del 5 febbraio 2026 ha una governance AI strutturata gestisce esattamente questo tipo di scelta in modo informato: il restante 91% sperimenta via API pubbliche senza DPIA e senza valutazione del rischio, sperando che la fortuna tenga.
Hai una PMI con dati sensibili o vincoli di data sovereignty, stai valutando un deployment AI e vuoi capire se self-hosted su Hetzner o un percorso diverso ha più senso per il tuo caso? Il modulo di preventivo gratuito ti dà una prima lettura in 7 domande, 2 minuti: ti dico con chiarezza se il tuo progetto rientra nelle cose che so fare bene, come si imposterebbe un primo confronto, quali domande aggiuntive ha senso farci. Se il caso richiede un profilo diverso dal mio, te lo dico e quando posso ti indico una direzione utile.