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

Diagrama mostrando como proteger endpoint LLM self-hosted com reverse proxy, chave de acesso e firewall na VPS
Resposta rápida

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 sudo na 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
Formação Recomendada

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 😀

  1. teste se a porta do modelo responde pela internet
  2. chame o endpoint pelo domínio SEM header de autenticação e confirme que a resposta é recusa
  3. chame o mesmo endpoint COM a credencial e confirme que ele responde
  4. 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.




Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares