Como proteger o endpoint do seu LLM self-hosted na VPS (chave de acesso, rate limit e portas fechadas)

Proteger endpoint LLM self-hosted é trabalho de camadas, não de um comando só. A API local do Ollama não exige autenticação, e o vLLM aceita chave via --api-key, mas a própria documentação avisa que ela cobre apenas /v1, /v2 e /inference. O caminho é confirmar em qual host e porta o modelo está ouvindo (vLLM em localhost:8000, Ollama em 127.0.0.1:11434), colocar um reverse proxy com TLS e credencial na frente (basic_auth no Caddy ou auth_basic no Nginx), aplicar rate limit e fechar a porta no ufw liberando só o IP de origem que consome o modelo.
Seu endpoint de modelo pode estar respondendo pra qualquer pessoa do planeta neste exato minuto, e ninguém te manda notificação avisando
Fala aí, beleza? Quem sobe vLLM ou Ollama numa VPS pra consumir de um n8n, de um app ou de um agente normalmente para no "funcionou"
O modelo responde, o fluxo roda, a vida segue
Aí o acesso nunca é fechado, e o detalhe chato é que parte dessas stacks não vem autenticada por padrão: não tem senha nenhuma na frente da API local
Pra ver que o risco não é teoria, uma pesquisa conjunta da SentinelLABS (SentinelOne) com a Censys identificou 175.000 hosts Ollama publicamente expostos na internet, distribuídos por 130 países
Não é bug exótico, é configuração padrão somada a um firewall aberto
Bora fechar isso? 🙂
O que você precisa antes de começar
- VPS com o modelo já rodando, vLLM ou Ollama
- acesso
sudona máquina - um domínio apontado pro IP da VPS, caso você vá usar TLS com o Caddy
- o IP de origem do serviço que vai consumir o modelo (o servidor do n8n, do app ou do agente)
Um aviso de escopo antes de descer pro passo a passo: os comandos variam por stack, e cada seção abaixo diz a qual produto pertence
Flag do vLLM não vale pro Ollama, variável do Ollama não vale pro vLLM
Se você trocar os dois de lugar, o resultado é um endpoint que você ACHA que está protegido
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 118 aulas
- 4 projetos
- 9h 33min
Passo a passo para proteger o endpoint do seu LLM self-hosted
A lógica aqui é de camadas: credencial, proxy com TLS, rate limit e firewall
Nenhuma delas sozinha resolve, e é justamente por isso que a ordem importa
1. Descubra onde o seu modelo está ouvindo hoje
Antes de proteger, você precisa saber o que está aberto
A documentação do vLLM diz que o servidor sobe em http://localhost:8000, e que o endereço pode ser alterado com os argumentos --host e --port
O Ollama liga por padrão em 127.0.0.1 na porta 11434, e esse bind é alterado pela variável de ambiente OLLAMA_HOST
Em vez de confiar na memória, olha o estado real da máquina:
ss -tlnp | grep -E ':8000|:11434'
E qual a diferença que isso faz? Toda
Se aparecer 127.0.0.1, o serviço só aceita conexão da própria máquina
Se aparecer 0.0.0.0, ele está escutando em todas as interfaces, incluindo a que dá de cara pra internet
O erro comum deste passo: assumir a interface de bind por memória ou por tutorial de terceiro, sem conferir na sua VPS qual valor está valendo agora
2. Ligue a chave de API no vLLM (e saiba o que ela NÃO cobre)
O servidor compatível com OpenAI do vLLM aceita uma chave de API via flag --api-key, ou pela variável de ambiente VLLM_API_KEY
O cliente passa a enviar essa chave como Bearer token:
vllm serve NousResearch/Meta-Llama-3-8B-Instruct --dtype auto --api-key token-abc123
Agora a parte que quase ninguém lê: a documentação de segurança do vLLM avisa que o --api-key autentica apenas os endpoints sob os prefixos /v1, /v2 e /inference
E a própria doc diz, com todas as letras, pra não depender só dele em produção
Ou seja: a chave é a primeira camada, não a única
Se mais de uma pessoa ou mais de um serviço vai consumir esse endpoint, pensa na gestão de credencial desde já, é a mesma dor de compartilhar acesso sem chave individual em qualquer ferramenta de IA
O erro comum deste passo: achar que --api-key tranca o servidor inteiro e parar a configuração de segurança aí
3. Entenda o caso Ollama: a API local não pede autenticação
Esse ponto merece uma pausa, porque é onde mora a pegadinha
A API local do Ollama (http://localhost:11434) não exige autenticação
A exigência de chave via header Authorization: Bearer pertence à API de nuvem do Ollama, servida em https://ollama.com/api, com a OLLAMA_API_KEY
Traduzindo: aquela API key que você viu na doc não protege o seu servidor, ela autentica você no serviço deles
Na sua VPS, quem protege o Ollama é bind, proxy e firewall
A forma documentada de configurar variáveis do Ollama no Linux com systemd é editar o serviço:
sudo systemctl edit ollama.service
Na seção Service do arquivo, você adiciona a linha de ambiente:
Environment="OLLAMA_HOST=0.0.0.0:11434"
Depois recarrega e reinicia:
sudo systemctl daemon-reload
sudo systemctl restart ollama
Tome cuidado aqui! OLLAMA_HOST=0.0.0.0:11434 expõe o Ollama em todas as interfaces
Só use esse valor DEPOIS de ter proxy com credencial e firewall fechado na frente, senão você acabou de publicar sua GPU
O erro comum deste passo: trocar pra 0.0.0.0 pra "conseguir acessar de fora" e deixar o resto pra depois, porque o depois raramente chega 😛
4. Ponha um reverse proxy com TLS e credencial na frente
Essa é a camada que transforma um endpoint solto em um endpoint publicável
No Caddy, o HTTPS automático é ativado quando o endereço do site contém um nome de host ou IP, e o reverse_proxy passa a ser servido sobre HTTPS automaticamente quando o Caddy conhece o domínio
A credencial vem da diretiva basic_auth, que não aceita senha em texto puro
O hash é gerado antes:
caddy hash-password
O comando imprime o hash pronto pra colar na configuração (o algoritmo padrão é bcrypt, e argon2id também está disponível):
seu-dominio.com.br {
basic_auth {
seu_usuario COLE_O_HASH_AQUI
}
reverse_proxy 127.0.0.1:8000
}
A doc do Caddy registra que basic auth não é seguro sobre HTTP puro, então TLS não é enfeite nesse arranjo, é requisito pra credencial fazer sentido
No Nginx, o equivalente é o módulo ngx_http_auth_basic_module, com duas diretivas:
auth_basic "closed site";
auth_basic_user_file conf/htpasswd;
O erro comum deste passo: o clássico do Nginx, errar o caminho do arquivo de senha
Os caminhos informados em auth_basic_user_file são relativos ao diretório do arquivo de configuração (nginx.conf), e não ao prefixo de instalação
5. Coloque rate limit na frente da GPU
Credencial impede o desconhecido, rate limit impede o conhecido de derrubar sua máquina
No Nginx, quem faz isso é o módulo ngx_http_limit_req_module, que limita a taxa de requisições por chave (por IP, por exemplo) usando o método leaky bucket
São duas diretivas: limit_req_zone define a zona de memória compartilhada e a taxa, e limit_req aplica essa zona com um burst
Requisições acima do burst máximo são encerradas com erro:
limit_req_zone $binary_remote_addr zone=llm:TAMANHO_DA_ZONA rate=SUA_TAXA;
limit_req zone=llm burst=SEU_BURST;
Por que placeholder no lugar dos números? Porque não existe taxa oficial recomendada pra endpoint de LLM
A taxa saudável depende do seu hardware, do tamanho do modelo e de quantas chamadas o seu fluxo dispara por execução, então esse valor sai da SUA medição, não de um post da internet
No Caddy a história é outra: rate limiting não faz parte da distribuição padrão
Quem resolve é o módulo de comunidade mholt/caddy-ratelimit, que não é repositório oficial da organização Caddy Web Server e precisa ser compilado no binário:
xcaddy build --with github.com/mholt/caddy-ratelimit
O erro comum deste passo: assumir que o Caddy já limita taxa por padrão e seguir a vida achando que tem uma proteção que não está instalada
6. Feche as portas e libere só a origem que consome
Agora a camada de rede
A documentação do vLLM orienta que portas e interfaces internas nunca sejam expostas à internet pública ou a redes não confiáveis, e recomenda controles de rede como firewall, segmentação e rede privada isolada
O ufw é instalado desabilitado, com política padrão de deny para entrada (incoming), deny para forward e allow para saída (outgoing)
Isso é ótimo, porque significa que o padrão já é fechado, desde que você habilite o firewall
A liberação por origem específica segue a sintaxe documentada no Ubuntu Server, por protocolo e origem:
sudo ufw allow proto tcp from 192.168.0.2 to any port 8000
A origem pode ser um IP ou uma faixa, tipo 192.168.0.0/24
Troca o 192.168.0.2 pelo IP do servidor que consome o modelo, e a porta pela do seu endpoint: 8000 no vLLM, 11434 no Ollama
Tome cuidado! Garanta a regra do seu próprio acesso SSH antes de habilitar o firewall, senão você se tranca do lado de fora da VPS
Outro detalhe que derruba configuração boa: no ufw a ordem das regras importa
A primeira regra que casa é aplicada e as demais não são avaliadas, então as regras específicas devem vir antes das genéricas
Quem está em cloud com security group tem a mesma lógica em outro lugar: a porta do modelo não fica aberta pra 0.0.0.0/0, ela fica liberada apenas pro endereço de origem do serviço que consome
O erro comum deste passo: escrever a regra genérica primeiro e a específica depois, e ficar sem entender por que a restrição não pegou
Como validar que o endpoint realmente exige credencial
Configuração aplicada não é configuração validada, beleza? Bora testar de fora
Rode os comandos abaixo da SUA máquina, nunca de dentro da VPS, porque de dentro tudo sempre parece funcionar 😀
- teste se a porta do modelo responde pela internet
- chame o endpoint pelo domínio SEM header de autenticação e confirme que a resposta é recusa
- chame o mesmo endpoint COM a credencial e confirme que ele responde
- repita o teste apontando direto pra porta original, pra garantir que ela não atende pela internet
# 1 e 4: a porta bruta responde de fora?
curl -i --max-time 5 http://SEU_IP:8000/
curl -i --max-time 5 http://SEU_IP:11434/
# 2: sem credencial, tem que recusar
curl -i https://seu-dominio.com.br/v1/ENDPOINT_QUE_VOCE_USA
# 3: com credencial, tem que responder
curl -i https://seu-dominio.com.br/v1/ENDPOINT_QUE_VOCE_USA \
-H "Authorization: Bearer token-abc123"
Se os comandos 1 e 4 derem timeout ou conexão recusada, é sinal de porta fechada pra fora
Se eles devolverem qualquer resposta, sua porta bruta está acessível e o proxy virou enfeite, porque existe um caminho que passa por fora dele
O erro comum deste passo: validar só pelo domínio, ver a recusa bonitinha e esquecer que a porta original continua aberta na internet
Que configuração usar em cada cenário
Nem todo mundo precisa de tudo, e é aqui que dá pra economizar dor de cabeça
| Cenário | Bind do modelo | Firewall | Proxy com TLS | Rate limit |
|---|---|---|---|---|
| Mesma máquina ou rede privada | loopback | porta fechada pra fora | não precisa | opcional |
| n8n ou app em outro servidor | conforme a stack | libera só o IP de origem | recomendado | recomendado |
| Acesso pela internet | atrás do proxy | só as portas do proxy | obrigatório | obrigatório |
a) O consumo é na mesma máquina ou na rede privada
Esse é o cenário mais tranquilo e o mais subestimado
Se quem chama o modelo roda no mesmo host, o bind em loopback já resolve: o vLLM sobe em localhost:8000 por padrão e o Ollama fica em 127.0.0.1:11434
Não mexe no OLLAMA_HOST, não abre porta no ufw, não publica nada
A proteção mais barata do mundo é a porta que nunca foi aberta 🙂
b) O n8n ou o app está em outro servidor
Aqui você precisa de acesso externo, mas não de acesso público
Libera a porta apenas pro IP de origem daquele servidor no ufw ou no security group, e mantém a credencial ativa no endpoint
Duas camadas, duas condições: quem não vem daquele IP nem chega, e quem chega ainda precisa apresentar a chave
Se o seu n8n também está numa VPS pública, vale passar o checklist de segurança do n8n no mesmo dia, porque endpoint protegido com orquestrador aberto continua sendo um problema aberto
c) Você precisa de acesso pela internet mesmo
Aí não tem atalho: reverse proxy com TLS, credencial e rate limit, os três
E a porta do modelo continua fechada pro mundo, atendendo só o proxy
A recomendação da doc do vLLM é bem clara nesse ponto: nunca expor portas e interfaces internas à internet pública ou a redes não confiáveis, tratando isolamento de rede como camada obrigatória
Isolamento de rede não é alternativa à credencial, é a camada que fica embaixo dela
Vídeo do canal pra complementar
Se você quer ver IA trabalhando em cima do seu próprio projeto pra começar do zero, este vídeo do canal mostra uma skill que faz a IA ler seu código e desenhar a arquitetura sozinha (Archify):
Próximo passo
Recapitulando a lógica toda: credencial no endpoint, proxy com TLS e rate limit na frente, firewall fechado embaixo
Nenhuma dessas camadas sozinha dá conta
A chave do vLLM cobre apenas /v1, /v2 e /inference e a própria doc pede medidas adicionais em produção
A API local do Ollama não pede autenticação nenhuma
Rate limit no Caddy depende de módulo de comunidade compilado
E o ufw só protege se estiver habilitado e com as regras na ordem certa
O próximo passo prático é bem curto: hoje mesmo, roda o ss -tlnp pra ver qual é o bind atual do seu modelo e quais portas a sua VPS expõe
Com esse retrato na mão, você aplica só a camada que estiver faltando, sem refazer o que já está de pé
até o próximo post! 😀
Perguntas frequentes
A chave de API do vLLM protege todos os endpoints do servidor?
Não. A documentação de segurança do vLLM avisa que o –api-key autentica só os endpoints sob os prefixos /v1, /v2 e /inference. Por isso a própria doc orienta não depender só dessa flag para proteger o vLLM em produção.
Preciso configurar a OLLAMA_API_KEY pra proteger meu Ollama na VPS?
Não, essa chave não serve pra isso. A OLLAMA_API_KEY com header Authorization: Bearer autentica a API de nuvem servida em https://ollama.com/api, e a API local em http://localhost:11434 não exige autenticação nenhuma. Quem protege o Ollama self-hosted é bind, proxy e firewall.
É seguro deixar OLLAMA_HOST em 0.0.0.0 sem mais nada?
Não. OLLAMA_HOST=0.0.0.0:11434 expõe o Ollama em todas as interfaces, e a API local não pede senha. Isso só é aceitável depois de ter proxy com credencial e firewall fechado na frente, senão o servidor fica exposto do jeito que a pesquisa da SentinelLABS com a Censys encontrou em 175.000 hosts Ollama em 130 países.
Basic auth no Caddy funciona sem certificado TLS?
Não é recomendado. A própria documentação do Caddy registra que basic auth não é seguro sobre HTTP puro. Como o HTTPS automático do Caddy já é ativado ao declarar o domínio no Caddyfile, o TLS entra de forma implícita junto com o basic_auth.
O Caddy tem rate limiting nativo pra proteger o endpoint de excesso de requisições?
Não na distribuição padrão. Rate limiting no Caddy vem do módulo de comunidade mholt/caddy-ratelimit, que não é repositório oficial da organização Caddy Web Server e precisa ser compilado no binário com xcaddy build –with github.com/mholt/caddy-ratelimit.
Como liberar a porta do endpoint só pro IP do meu n8n no firewall?
Com o ufw, a sintaxe documentada é por protocolo e origem, como em sudo ufw allow proto tcp from 192.168.0.2 to any port 8000, trocando a porta pela do seu endpoint (8000 no vLLM, 11434 no Ollama) e a origem pelo IP do servidor que consome, que também pode ser uma faixa como 192.168.0.0/24. Como a ordem das regras importa no ufw, essa regra específica precisa vir antes de qualquer regra genérica que libere a porta pra todo mundo.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
O que significa “ChatGPT network error” e como resolver
O “ChatGPT Network Error” é uma ocorrência frequente na rotina de muitos usuários do ChatGPT. Porém, poucos compreendem seu significado, quando esse erro surge, etc. […]

Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]

Como usar o Antigravity do Google: guia completo do zero ao primeiro app
Aprenda neste guia prático como usar o Antigravity do Google: descubra a instalação, configuração, criação de projetos com o Agent Manager e o primeiro deploy, […]
