---
title: "Graceful Shutdown em Go: Deploy sem Derrubar Requests"
url: "https://golang.com.br/blog/graceful-shutdown-go-producao/"
markdown_url: "https://golang.com.br/blog/graceful-shutdown-go-producao.MD"
description: "Aprenda graceful shutdown em Go para HTTP servers, workers, Kubernetes, SIGTERM, context, health checks, filas e deploys sem perder requisições."
date: "2026-05-27"
author: "Golang Brasil"
---

# Graceful Shutdown em Go: Deploy sem Derrubar Requests

Aprenda graceful shutdown em Go para HTTP servers, workers, Kubernetes, SIGTERM, context, health checks, filas e deploys sem perder requisições.


Graceful shutdown em Go é o que separa um exemplo que roda localmente de um serviço que aguenta deploy, escala, rollback e reinício sem derrubar usuários no meio do caminho. A ideia parece simples: quando o processo recebe um sinal de término, ele para de aceitar trabalho novo, dá alguns segundos para terminar o trabalho em andamento e encerra de forma previsível. Na prática, muita API Go ainda morre no `SIGTERM` como se fosse um script descartável.

Isso cobra preço em produção. Uma requisição HTTP pode ser interrompida depois de gravar parte dos dados. Um worker pode confirmar uma mensagem antes de terminar o processamento. Um consumidor Kafka pode perder controle de offset. Um servidor em Kubernetes pode continuar recebendo tráfego por alguns segundos depois de começar a encerrar. Um deploy que deveria ser invisível vira pico de erro 5xx, job duplicado ou goroutine vazando.

Este guia mostra como implementar shutdown gracioso em serviços Go reais: HTTP server, `context.Context`, sinais do sistema, readiness probe, workers, filas e observabilidade. Ele complementa os guias de [context.Context em Go](/blog/context-timeout-cancelamento-go/), [worker pool em Go](/blog/worker-pool-go-fila-jobs/), [API REST em Go](/aprenda/api-rest-go/) e [Go com Kubernetes](/tutoriais/go-kubernetes/).

## O que acontece durante um deploy

Em um ambiente moderno, seu processo raramente encerra porque alguém apertou `Ctrl+C` no terminal. Ele encerra porque uma plataforma pediu: Kubernetes troca um Pod, systemd reinicia um serviço, Cloud Run escala para baixo, ECS substitui uma task ou um deploy azul-verde move tráfego para outra versão.

O fluxo típico é este:

1. A plataforma decide terminar a instância antiga.
2. O processo recebe `SIGTERM`.
3. O load balancer ou service mesh começa a remover a instância do tráfego.
4. Requisições que já estavam em andamento ainda precisam terminar.
5. Workers precisam parar de pegar jobs novos e finalizar ou devolver os atuais.
6. Depois de um prazo, se o processo não saiu, a plataforma envia `SIGKILL`.

O ponto crítico é que esses passos não são instantâneos. Mesmo quando Kubernetes marca o Pod como terminating, ainda pode existir tráfego chegando por alguns segundos. Se sua aplicação apenas chama `os.Exit(0)` ou deixa o processo morrer, ela interrompe conexões ativas sem coordenação.

## O esqueleto mínimo com net/http

O pacote `net/http` já tem suporte ao shutdown gracioso. O método `Server.Shutdown(ctx)` para de aceitar novas conexões, fecha listeners e espera handlers ativos terminarem até o contexto expirar.

Um `main` mínimo pode começar assim:

```go
package main

import (
    "context"
    "errors"
    "log/slog"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        _, _ = w.Write([]byte("ok"))
    })

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
    }

    errCh := make(chan error, 1)
    go func() {
        slog.Info("servidor iniciado", "addr", srv.Addr)
        errCh <- srv.ListenAndServe()
    }()

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)

    select {
    case sig := <-sigCh:
        slog.Info("sinal de encerramento recebido", "signal", sig.String())
    case err := <-errCh:
        if !errors.Is(err, http.ErrServerClosed) {
            slog.Error("servidor encerrou com erro", "error", err)
            os.Exit(1)
        }
    }

    ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        slog.Error("shutdown excedeu prazo", "error", err)
        _ = srv.Close()
        os.Exit(1)
    }

    slog.Info("shutdown concluído")
}
```

Esse código cobre o básico, mas o desenho operacional importa tanto quanto a chamada. O timeout precisa ser menor que o prazo total dado pela plataforma. Se Kubernetes dá 30 segundos de `terminationGracePeriodSeconds`, não configure `Shutdown` para 30 segundos exatos. Reserve margem para propagação de readiness, flush de logs, fechamento de conexões e saída do processo.

## Readiness: pare de receber tráfego antes de encerrar

Em Kubernetes, o servidor pode continuar recebendo tráfego enquanto endpoints e proxies atualizam estado. Por isso, um padrão seguro é separar `liveness` de `readiness`.

- `liveness`: responde se o processo está vivo e não travado.
- `readiness`: responde se a instância ainda deve receber tráfego novo.

Quando o shutdown começa, marque a instância como não pronta antes de chamar `Server.Shutdown`. Isso dá ao roteamento uma chance de parar de enviar requisições novas.

```go
var shuttingDown atomic.Bool

mux.HandleFunc("GET /readyz", func(w http.ResponseWriter, r *http.Request) {
    if shuttingDown.Load() {
        http.Error(w, "shutting down", http.StatusServiceUnavailable)
        return
    }
    w.WriteHeader(http.StatusOK)
})
```

No encerramento:

```go
shuttingDown.Store(true)
time.Sleep(5 * time.Second) // pequena janela para o load balancer reagir
```

Esse `Sleep` parece feio, mas é uma prática comum quando a infraestrutura precisa de tempo para propagar o estado. O valor deve ser pequeno, medido e alinhado ao ambiente. Em clusters com service mesh, ingress controller e balanceador externo, a janela pode variar.

## Handlers precisam respeitar context

`Server.Shutdown` não cancela magicamente todo trabalho interno. Ele espera os handlers terminarem. Se um handler chama banco, API externa ou fila sem usar o contexto da requisição, ele pode ficar preso até o timeout global estourar.

O padrão correto é propagar `r.Context()`:

```go
func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    order, err := h.orders.Create(ctx, decodeOrder(r.Body))
    if err != nil {
        http.Error(w, "erro ao criar pedido", http.StatusInternalServerError)
        return
    }

    _ = json.NewEncoder(w).Encode(order)
}
```

Camadas internas também devem receber contexto como primeiro argumento. Repositórios usam `QueryContext`; clients HTTP usam `http.NewRequestWithContext`; producers de fila devem aceitar cancelamento quando a publicação demora. O shutdown gracioso começa no `main`, mas só funciona se o restante do código respeita o contrato.

## Workers: pare de consumir antes de matar o processo

Workers exigem cuidado diferente de APIs. Em HTTP, a plataforma normalmente sabe parar tráfego. Em filas, o próprio worker decide quando buscar a próxima mensagem. Durante shutdown, ele deve parar de consumir mensagens novas e resolver o que já pegou.

O fluxo seguro é:

1. Cancelar o contexto raiz.
2. Parar polling ou consumo de mensagens novas.
3. Esperar jobs em andamento terminarem dentro de um prazo.
4. Confirmar apenas mensagens concluídas.
5. Devolver, reencaminhar ou deixar expirar mensagens não concluídas.

Em uma fila com ack explícito, nunca confirme antes de terminar o trabalho. Em SQS, ajuste o visibility timeout para que uma task interrompida volte para a fila. Em RabbitMQ, faça `ack` apenas depois do processamento. Em Kafka, avance offset somente após finalizar a unidade de trabalho que aquele offset representa.

Para worker pools dentro do processo, combine `context`, `WaitGroup` e fechamento controlado de channels:

```go
func runWorker(ctx context.Context, jobs <-chan Job, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-jobs:
            if !ok {
                return
            }
            process(ctx, job)
        }
    }
}
```

Esse exemplo é simples de propósito. Em produção, `process` deve diferenciar erro transitório, erro definitivo e cancelamento. Se `ctx.Err()` foi causado por shutdown, talvez o melhor seja parar sem transformar isso em alerta crítico.

## Banco, cache e conexões externas

Nem todo recurso precisa ser fechado manualmente no mesmo momento. O importante é fechar depois que parou de aceitar trabalho novo e depois que as operações em andamento tiveram chance de terminar.

Uma ordem comum é:

1. Marcar readiness como falsa.
2. Parar server HTTP ou consumidor de fila.
3. Esperar handlers/workers ativos.
4. Fazer flush de telemetria.
5. Fechar pools de banco, Redis, producers e exporters.

Fechar o pool de banco antes dos handlers terminarem cria erro artificial. Fechar exporters antes do final perde o log ou trace justamente do shutdown. Por outro lado, deixar conexões abertas até o `SIGKILL` dificulta diagnosticar vazamentos.

## Observabilidade do shutdown

Shutdown não deve ser invisível. Registre logs estruturados e métricas para responder perguntas simples:

- Quantos shutdowns aconteceram nas últimas 24 horas?
- Quanto tempo cada instância levou para encerrar?
- Quantas requisições ainda estavam ativas quando o sinal chegou?
- Quantos jobs foram concluídos, devolvidos ou interrompidos?
- O timeout de shutdown estourou alguma vez?

Com `slog`, registre o sinal recebido, o prazo configurado, a duração total e o resultado. Em Prometheus, conte shutdowns e exponha histogramas de duração. Em tracing, faça flush antes de sair. O objetivo não é transformar deploy normal em incidente; é conseguir diferenciar deploy saudável de encerramento forçado.

## Erros comuns em Go

O primeiro erro é usar `log.Fatal` dentro de goroutine que roda o servidor. `log.Fatal` chama `os.Exit(1)` e pula `defer`, então você perde a chance de executar shutdown. Prefira enviar erro para um channel e decidir no `main`.

O segundo erro é tratar `http.ErrServerClosed` como falha. Quando `Shutdown` fecha o servidor, `ListenAndServe` retorna esse erro por design. Seu código precisa ignorá-lo no caminho esperado.

O terceiro é esquecer timeouts em clients externos. Um handler que chama API de pagamento sem timeout pode segurar o shutdown até o limite da plataforma. Combine timeout por requisição com timeout global de encerramento.

O quarto é fazer shutdown longo demais. Se o deploy precisa de dois minutos para drenar, talvez o problema esteja em handlers lentos, jobs grandes demais ou falta de checkpoint. Graceful shutdown é rede de segurança, não desculpa para operações sem limite.

## Checklist para produção

Antes de considerar um serviço Go pronto para deploy contínuo, revise:

- `SIGTERM` e `os.Interrupt` são capturados no `main`.
- `Server.Shutdown(ctx)` é chamado com timeout menor que o prazo da plataforma.
- Readiness muda para falso no início do encerramento.
- Handlers propagam `r.Context()` para banco, cache, clients e filas.
- Workers param de consumir trabalho novo antes de esperar jobs ativos.
- Mensagens só recebem ack depois do processamento completo.
- Logs e métricas registram início, duração e resultado do shutdown.
- `http.ErrServerClosed` não vira alerta falso.
- Testes ou ensaios de deploy validam o comportamento com tráfego realista.

## Conclusão

Graceful shutdown em Go não é uma biblioteca mágica. É uma disciplina de ciclo de vida: aceitar trabalho, processar com contexto, parar de receber, drenar o que está em andamento, fechar recursos e sair dentro do prazo. O Go fornece peças boas para isso, mas a aplicação precisa conectá-las.

Se você está construindo APIs, workers ou microserviços, implemente esse padrão cedo. Ele melhora deploy, rollback, escalabilidade e confiança operacional. Depois, combine com [observabilidade em Go](/tutoriais/go-observability/), [Prometheus em Go](/tutoriais/go-prometheus/), [mensageria em Go](/blog/mensageria-go-rabbitmq-kafka-nats-sqs/) e [Docker para Go](/aprenda/golang-docker/). Para comparar como outra linguagem de sistemas trata ciclo de vida de serviços, o portal <a href="https://rustlang.com.br/artigos/rust-vs-go/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust Brasil compara Go e Rust em backend, concorrência e performance</a>.
