Graceful Shutdown & Signal Handling
Trate SIGTERM e SIGINT para que servidores HTTP finalizem requisições em andamento, workers em background parem de forma limpa e deploys em rolling do Kubernetes não percam conexões ativas.
Busque em todas as páginas da documentação
Trate SIGTERM e SIGINT para que servidores HTTP finalizem requisições em andamento, workers em background parem de forma limpa e deploys em rolling do Kubernetes não percam conexões ativas.
Orquestradores enviam SIGTERM antes de matar um container.
http.Server.Shutdown para de aceitar novas conexões e espera por requisições ativas até um deadline do context.
Goroutines em background precisam de cancelamento explícito via context.Context ou fechamento de canais.
golang.org/x/sync/errgroup coordena múltiplos listeners e workers para encerrarem juntos.
Cartão de receita de referência rápida - pronto para copiar e colar.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_ = server.Shutdown(ctx)Quando usar isso:
package main
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
mux := http.NewServeMux()
mux.HandleFunc("GET /work", func(w http.ResponseWriter, r *http.Request) {
select {
case <-time.After(2 * time.Second):
w.Write([]byte("done"))
case <-r.Context().Done():
return
}
})
api := &http.Server{Addr: ":8080", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
admin := &http.Server{Addr: ":9090", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("metrics"))
})}
g, gctx := errgroup.WithContext(ctx)
g.Go(func() error {
slog.Info("api listening", "addr", api.Addr)
if err := api.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
})
g.Go(func() error {
slog.Info("admin listening", "addr", admin.Addr)
if err := admin.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
})
g.Go(func() error {
<-gctx.Done()
slog.Info("shutdown started")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := api.Shutdown(shutdownCtx); err != nil {
slog.Error("api shutdown", "err", err)
}
if err := admin.Shutdown(shutdownCtx); err != nil {
slog.Error("admin shutdown", "err", err)
}
slog.Info("shutdown complete")
return nil
})
if err := g.Wait(); err != nil {
slog.Error("server error", "err", err)
os.Exit(1)
}
}O que isso demonstra:
signal.NotifyContext cancela o context raiz em SIGTERMShutdown em cada servidorr.Context() durante o drainListenAndServe bloqueia até que Shutdown feche o listener.Shutdown marca o servidor como fechado, fecha conexões ociosas e espera por handlers ativos.r.Context().Done() para sair prontamente quando clientes desconectam.| Step | Action |
|---|---|
| 1 | Pare de aceitar novo trabalho HTTP/gRPC (Shutdown) |
| 2 | Cancele o context do worker para que os consumidores parem de fazer polling |
| 3 | Espere pela conclusão do handler em andamento e do worker |
| 4 | Feche pools e clientes de DB |
| 5 | Descarregue telemetria (TracerProvider.Shutdown) |
| Source | Typical value |
|---|---|
| App shutdown context | 10-30s |
Kubernetes terminationGracePeriodSeconds | 30s default |
| Load balancer deregistration delay | Contabilize no orçamento total |
// Workers vinculados ao context de shutdown
func runWorker(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
processOneJob(ctx)
}
}
}os.Exit sem Shutdown - Perde requisições ativas no deploy; sempre drene primeiro.Shutdown no main sem goroutine - Não é possível tratar sinal na mesma thread se não for estruturado corretamente.http.ErrServerClosed - Normal após Shutdown; trate como sucesso, não falha.| Alternative | Use When | Don't Use When |
|---|---|---|
signal.NotifyContext | Serviços Go 1.16+ modernos | Você precisa de padrões de canal de sinal legados |
| Manual signal channel | Controle granular sobre sinais | NotifyContext mais simples é suficiente |
errgroup | Múltiplos servidores ou workers | Único ListenAndServe em binários minúsculos |
Close em vez de Shutdown | Parada forçada imediata (apenas testes) | Deploys de produção |
Shutdown drena requisições ativas graciosamente.
Close fecha conexões imediatamente.
Defina readiness como false (ou implemente um sleep no hook preStop) antes de chamar Shutdown para que os endpoints drenem.
Frequentemente sim - cancele um context raiz compartilhado em SIGTERM para que todos os subsistemas parem juntos.
Registre o trabalho restante, opcionalmente chame Close no servidor e saia com código não-zero para observabilidade.
Conexões de longa duração precisam de tratamento explícito de fechamento nos handlers; o drain padrão pode não encerrá-las rapidamente.
Inicie o servidor em uma goroutine, envie SIGTERM para si mesmo no teste, e afirme que o handler completa dentro do timeout.
Use grpc.Server.GracefulStop() com um fallback de timeout para Stop() para terminação forçada.
Comum para recarregar configurações de rotação de logs; separado do tratamento de SIGTERM de deploy.
Chame uma vez por instância de servidor; chamadas duplicadas retornam ErrServerClosed.
Ele propaga o primeiro erro e espera por todas as goroutines, útil quando servidores admin e API rodam juntos.
Registre o sinal recebido, início do drain, conclusão por subsistema e saída final para correlação de deploy.
CLIs curtas geralmente saem apenas no cancelamento do context.
Daemons e servidores de longa duração precisam de padrões de drain completos.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026