Ловишь сигнал: quit := make(chan os.Signal, 1); signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM). В горутине <-quit — получили сигнал. Вызываешь server.Shutdown(ctx) — перестаёт принимать новые соединения, дожидается текущих. Context с таймаутом — чтобы не ждать вечно. Для воркеров — закрываешь канал задач, ждёшь WaitGroup. Для БД — закрываешь пул. Порядок важен: сначала HTTP сервер, потом воркеры, потом ресурсы. Без graceful shutdown — потеря данных и обрыв запросов.