---
title: "TLS, HTTPS e mTLS em Zig: Terminação, Certificados e Conexões Seguras em Produção"
url: "https://ziglang.com.br/artigos/zig-tls-https-certificados-mtls/"
markdown_url: "https://ziglang.com.br/artigos/zig-tls-https-certificados-mtls.MD"
description: "Como servir HTTPS e abrir conexões TLS em Zig: terminação na borda, certificados locais, ClientHello/SNI, mTLS entre serviços, rotação de certificados e armadilhas de produção sem framework pesado."
date: "2026-06-21"
author: ""
---

# TLS, HTTPS e mTLS em Zig: Terminação, Certificados e Conexões Seguras em Produção

Como servir HTTPS e abrir conexões TLS em Zig: terminação na borda, certificados locais, ClientHello/SNI, mTLS entre serviços, rotação de certificados e armadilhas de produção sem framework pesado.


Servir HTTP em texto plano serve para desenvolvimento local e quase nada mais. Em produção, todo tráfego que cruza a rede — entre o navegador e seu serviço, entre dois microsserviços, entre um worker e um broker — precisa de **TLS**. Sem ele, qualquer hop intermediário consegue ler, modificar ou regravar a conversa. O engraçado é que a maior parte da confusão em torno de TLS não vem do algoritmo em si, e sim de quem termina a conexão, quem valida o quê, e como os certificados entram e saem do processo.

Em Zig, você raramente vai reimplementar OpenSSL. A biblioteca padrão já expõe primitivas de TLS tanto para servidor quanto para cliente, e o ecossistema converge para os mesmos padrões do resto do mercado. Este guia é sobre como pensar TLS em Zig na prática: terminação na borda, terminação no processo, **mTLS** entre serviços, certificados e rotação. Ele complementa os guias de [servidor HTTP em Zig para produção](/artigos/zig-http-server-producao/), [rede com sockets TCP e UDP](/artigos/zig-networking-sockets-tcp-udp/), [configuração segura com segredos e env](/artigos/zig-configuracao-segura-segredos-env/) e [observabilidade em Zig](/artigos/zig-observabilidade/). A mentalidade é a mesma: falha explícita, comportamento previsível e nada de mágica de framework.

## Onde o TLS termina

A primeira decisão de projeto não é "como", é "onde". TLS tem custo de CPU, custo de configuração e custo operacional. O lugar onde ele termina define boa parte da sua superfície de ataque.

| Topologia | Onde termina | Quando usar |
|---|---|---|
| Edge / reverse proxy | Nginx, Caddy, Traefik, balanceador de nuvem | Serviço exposto à internet, automação de Let's Encrypt |
| No processo (in-process) | A própria aplicação Zig | Backend isolado, mTLS interno, restrição de rede |
| Service mesh | Sidecar (Linkerd, Istio) | Muitos serviços, política centralizada |
| Sem TLS | Rede privada confiável e curta | Apenas dev/local, nunca produção |

A regra prática: se o tráfego sai de uma rede que você controla, ele precisa de TLS. Se ele entra por um navegador, termina em um proxy de borda com redirecionamento HTTP → HTTPS, HSTS e uma AC pública (Let's Encrypt, ZeroSSL). Se ele corre entre dois pods do mesmo cluster, mTLS com uma AC interna (Step, Smallstep, `cert-manager` + uma CA privada) resolve.

O armadilha clássica é a topologia "TLS na borda, HTTP puro da borda até o pod" sem proteção adicional. Em clusters bem desenhados isso é aceitável porque o plano de dados é confiável. Em redes compartilhadas, ele abre espaço para interceptação lateral. **mTLS ponta a ponta** elimina a ambiguidade.

## Certificados como arquivos, não como abstração

Um certificado X.509 é um arquivo PEM com três partes que importam: a chave privada, a folha assinada e a cadeia intermediária. Em produção, trate todos como segredos. Carregue do disco ou de um cofre; nunca embuta no binário; nunca registre em log.

Convenção útil, alinhada ao guia de [configuração segura com segredos e env](/artigos/zig-configuracao-segura-segredos-env/):

```text
/etc/zigapp/tls/
  fullchain.pem   # folha + intermediários
  privkey.pem     # 0600, dono do serviço
  ca.pem          # CA usada para validar mTLS do cliente
```

A aplicação lê esses caminhos do ambiente (`TLS_CERT`, `TLS_KEY`, `TLS_CLIENT_CA`) e falha rápido na inicialização se algum estiver ausente ou ilegível. Falhar cedo é melhor do que descobrir o problema no meio da noite com uma conexão pendurada.

## SNI e ClientHello: por que o nome importa

O **Server Name Indication (SNI)** é o que permite hospedar múltiplos domínios no mesmo IP com certificados diferentes. O cliente envia o nome do host no `ClientHello`, antes do handshake completar, e o servidor escolhe o certificado correto a partir dali.

Em Zig, ao construir um cliente TLS, passe sempre o nome do host que você está conectando. Não apenas o IP. Sem o nome, o servidor pode devolver o certificado errado e a validação falha — ou pior, devolve o certificado default e você fica sem perceber.

```zig
const std = @import("std");
const tls = std.crypto.tls;

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    var ctx = try tls.Client.init(allocator, .{
        .host = "ziglang.com.br",
        .ca = .{ .bundle = try std.fs.cwd().readFileAlloc(
            allocator,
            "/etc/ssl/certs/ca-certificates.crt",
            1 << 20,
        ) },
    });
    defer ctx.deinit(allocator);

    // A partir daqui ctx encapsula o estado do cliente TLS.
    // O nome em .host vira SNI no ClientHello e é comparado com o CN/SAN do certificado.
}
```

O detalhe que quebra produção: o nome do host precisa bater com o **Subject Alternative Name (SAN)** do certificado, não com o `Common Name`. Navegadores modernos ignoram o `Common Name`. O mesmo vale para bibliotecas atualizadas.

## mTLS entre serviços

**Mutual TLS** (mTLS) é TLS com uma volta extra: o servidor também exige um certificado do cliente. É o padrão de ouro para comunicação serviço-a-serviço porque dispensa tokens de longa duração em redes internas — a identidade está atada ao certificado, que tem data de expiração curta.

O fluxo:

1. O servidor carrega `TLS_CERT` + `TLS_KEY` como em qualquer HTTPS.
2. O servidor também carrega `TLS_CLIENT_CA` e configura o handshake para exigir e validar o certificado do cliente.
3. O cliente carrega seu próprio certificado cliente + chave.
4. O cliente valida o certificado do servidor (cadeia pública).
5. O servidor valida o certificado do cliente (cadeia privada interna).
6. Os dois lados extraem a identidade do SAN do cliente para autorização.

A regra de ouro: **certificados de cliente curtos**. Um par válido por 24 horas, renovado por um processo automático (Step-CA, SPIFFE, `cert-manager`), é dramaticamente mais seguro do que um token JWT de 30 dias. Se um certificado vaza, a janela útil é curta.

## Rotação sem reiniciar

A parte que mais assusta operação é a rotação. Você renovou o certificado no disco; como a aplicação passa a usá-lo sem cair?

Há três caminhos comuns:

1. **Reload por sinal.** A aplicação captura `SIGHUP`, recarrega os PEMs do disco e troca o estado TLS ativo de forma atômica. Conexões novas usam o novo certificado; conexões antigas terminam com o antigo.
2. **Reload por watchers.** Um watcher (`inotify` no Linux, `kqueue` no BSD) detecta a mudança no arquivo e dispara o mesmo caminho.
3. **Reload por deploy.** Reimplanta a aplicação com o novo certificado embutido no volume. Simples, mas força janela de indisponibilidade.

O caminho 1 é o equilíbrio típico. A implementação Zig mantém um ponteiro atômico para a configuração TLS corrente; o handler de `SIGHUP` carrega a nova, faz o `swap` e libera a antiga quando a última conexão fecha. O padrão é o mesmo usado para recarregar [rate limiting](/artigos/zig-rate-limiting-token-bucket/) e [cache TTL](/artigos/zig-cache-lru-ttl-producao/) sem reiniciar.

## Armadilhas de produção

**Validação desligada.** Em dev é tentador setar `insecure_skip_verify`. Em produção, isso transforma TLS em teatro. Nunca suba com validação desligada.

**Cipher suites desatualizadas.** TLS 1.2 ainda é aceitável, mas TLS 1.3 é o default moderno. Restrinja explicitamente aos ciphers recomendados (TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256). Desligue tudo abaixo de TLS 1.2.

**Pinning mal feito.** Pin de certificado inteiro quebra na primeira rotação. Pin de **chave pública (SPKI)** sobrevive enquanto a chave privada não rotaciona.

**Sem OCSP stapling.** Clientes que validam OCSP adicionam uma viagem extra por handshake. Servidores que fazem stapling entregam a resposta OCSP junto com o certificado, economizando um round-trip.

**Log de segredos.** Nunca logue a chave privada. Logue o fingerprint (SHA-256) para correlação, nunca o conteúdo.

**Timeout de handshake.** Handshake TLS não tem timeout natural; um cliente malicioso pode segurar o socket. Envolva o socket com um deadline e aborte o handshake se passar de poucos segundos.

## Verificação

Antes de subir, verifique com ferramentas que não são a sua:

- `curl -v https://seu.servico/` para validar cadeia, SNI e protocolo negociado.
- `openssl s_client -connect host:443 -servername host -showcerts` para inspecionar o certificado devolvido.
- `testssl.sh` ou **SSL Labs** para auditoria externa de configuração.
- `step ca certificate` para validar mTLS ponta a ponta.

A regra geral, que também vale para [circuit breaker e retry](/artigos/zig-circuit-breaker-timeout-retry/) e para [JWT em APIs Zig](/artigos/zig-jwt-autenticacao-api/): segurança que você não consegue medir a partir de fora, você não tem.

## Conclusão

TLS em Zig segue o mesmo princípio do resto do ecossistema da linguagem: poucas camadas, falha explícita e controle total do que está acontecendo. A biblioteca padrão dá o que precisa; o que falta é disciplina operacional — onde termina a conexão, como os certificados entram e saem, como roda a rotação. Quem trata TLS como infraestrutura e não como detalhe, dorme melhor. Para padrões mais amplos de rede e estratégia, vale também o material sobre [serviços Go em produção](https://golang.com.br/blog/go-producao-observabilidade/) na rede Diego, que cobre o mesmo modelo mental de terminação e rotação aplicado a outra stack.
