HTTP 429 Too Many Requests:原因、修復和速率限制
您在星期五下午部署(我知道、我知道),一切看起來都很好,然後您的監控燈亮得像聖誕樹一樣。用戶收到 429 錯誤。您的 API 拒絕了請求。或者可能是另一種情況——您在調用第三方 API,而他們在拒絕您。無論如何,HTTP 429 Too Many Requests 狀態碼剛剛成為您今天最重要的事情。
我經歷過這兩種情況。我曾經是那個因為構建流程配置不當而意外 DDoS CMS API 的開發人員,我也是實現速率限制來保護我們自己的伺服器免受失控客戶端的人。這兩種經驗都教會了我文檔中沒有涵蓋的內容。讓我們逐步了解所有這些。
目錄
- HTTP 429 實際上是什麼意思?
- 429 錯誤的常見原因
- Retry-After 標頭解釋
- 作為客戶端如何處理 429 錯誤
- 在 Next.js API 路由中實現速率限制
- 速率限制策略比較
- Astro 及其他框架中的速率限制
- 在生產環境中監控和調試 429 錯誤
- 常見問題

HTTP 429 實際上是什麼意思?
HTTP 429 在 RFC 6585 中定義,發佈於 2012 年。該規範出人意料地簡短。以下是要點:用戶(或客戶端)在給定時間內發送了太多請求。
就這樣。這是一個速率限制響應。伺服器在說,「我理解您的請求,它可能是有效的,但您需要放慢速度。」
這不同於 403 Forbidden(您不被允許)或 503 Service Unavailable(整個伺服器在掙扎)。429 是有針對性的。它是關於您的請求速率的具體問題。
響應應該包含一個 Retry-After 標頭,告訴客戶端在再次嘗試之前要等待多長時間。我說「應該」是因為許多 API 根本不費力,這使每個人的生活都變得更加困難。
您將在野外看到 429s 的地方
- 第三方 API:Stripe、OpenAI、GitHub、Contentful、Sanity——他們都有速率限制
- CDN 和託管平台:Vercel、Cloudflare 和 AWS 如果您觸發了他們的邊緣速率限制,將返回 429s
- 您自己的 API:如果您已實現速率限制(您應該)
- 構建流程:為每個頁面都觸發 CMS API 的靜態網站生成很容易觸發速率限制
- 網路爬蟲:如果您積極地從外部源獲取數據
429 錯誤的常見原因
讓我按照我在生產環境中實際遇到的場景進行細分,大致按出現頻率排列。
1. 靜態網站構建轟炸無頭 CMS
這是在無頭架構上工作的團隊最常遇到的問題。您有一個包含 2,000 個頁面的網站,每個頁面都需要來自您的 CMS 的數據。您的構建流程並行發送所有這些請求,CMS 看到大規模峰值,並開始返回 429s。您的構建失敗了。
我們在無頭 CMS 項目上定期看到這個問題。修複涉及請求隊列和並發限制,我將在下面介紹。
2. 缺少或損壞的快取
如果每次頁面加載都觸發新的 API 呼叫,因為您的快取層不工作,您會快速命中速率限制——特別是在流量峰值期間。我曾調試過一個 Next.js 應用,其中 revalidate 意外設置為 0,這意味著 ISR 實際上被禁用了。每個訪問者都觸發了對 Contentful 的新 API 呼叫。花費約 45 分鐘的真實流量才開始收到 429s。
3. 沒有退避的重試迴圈
您的代碼收到錯誤,立即重試,收到另一個錯誤,立即重試...恭喜,您已經構建了一個觸發速率限制的機器。我在 webhook 處理器、後台作業,甚至客戶端 fetch 呼叫中看到過這個模式。
4. 多個服務共享一個 API 金鑰
您的暫存環境、您的生產環境、您的本地開發設置和您的 CI/CD 管道都使用相同的 API 金鑰。每個看起來都很好,但總的來說,他們正在快速消耗您的速率限制預算。
5. 沒有防抖的客戶端 Fetch
搜索即時輸入功能,在每次按鍵時觸發 API 呼叫。一個每 500 毫秒輪詢一次的儀表板。一個觸發提取速度比用戶滾動速度快的無限滾動。這些模式絕對可以觸發 429s,特別是當在所有用戶中倍增時。
6. 實際濫用或攻擊
有時候 429 正在做它應該做的——保護您的伺服器免受某人發送不合理數量請求的影響。機器人、認證破解、爬蟲——速率限制是您的第一道防線。
Retry-After 標頭解釋
Retry-After 標頭是伺服器告訴您何時重新嘗試的方式。它可以採用兩種格式:
等待秒數:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
特定日期/時間:
HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 01 Jan 2026 00:00:00 GMT
秒數格式要常見得多。日期格式使用 RFC 7231 中定義的 HTTP-date。
以下是大多數教程不會告訴您的:許多 API 根本不發送 Retry-After,或者他們不一致地發送它。OpenAI 的 API 通常包含它。GitHub 的 API 包含它以及 X-RateLimit-Reset。許多較小的 API 只是發送一個裸露的 429,讓您猜測。
一些 API 還發送額外的速率限制標頭:
| 標頭 | 目的 | 示例 |
|---|---|---|
X-RateLimit-Limit |
每個窗口允許的最大請求數 | 100 |
X-RateLimit-Remaining |
當前窗口中剩餘的請求數 | 0 |
X-RateLimit-Reset |
窗口重置時的 Unix 時間戳 | 1735689600 |
Retry-After |
重試前等待的秒數 | 30 |
始終檢查這些標頭。它們讓您實現更聰明的重試邏輯,甚至可以在觸及限制之前主動放慢速度。

作為客戶端如何處理 429 錯誤
當您收到 429 錯誤時,以下是正確的處理方法。
帶有抖動的指數退避
這是黃金標準。不要只等待固定的時間——隨著每次重試指數增加延遲,並添加一些隨機性(抖動)以防止雷鳴羊群問題。
async function fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries: number = 5
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error(`Still getting 429 after ${maxRetries} retries`);
}
// Check for Retry-After header first
const retryAfter = response.headers.get('Retry-After');
let delay: number;
if (retryAfter) {
// Could be seconds or a date
const parsed = parseInt(retryAfter, 10);
if (!isNaN(parsed)) {
delay = parsed * 1000;
} else {
delay = new Date(retryAfter).getTime() - Date.now();
}
} else {
// Exponential backoff with jitter
const baseDelay = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * 1000;
delay = baseDelay + jitter;
}
console.log(`Rate limited. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}`);
await new Promise(resolve => setTimeout(resolve, delay));
}
// TypeScript wants this, though we'll never reach it
throw new Error('Unexpected end of retry loop');
}
構建流程的請求隊列
對於需要進行數百或數千個 API 呼叫的靜態網站生成,使用帶有並發控制的隊列:
import pLimit from 'p-limit';
// Limit to 5 concurrent requests
const limit = pLimit(5);
const pages = await getAllPageSlugs(); // Returns ['/', '/about', '/blog/post-1', ...]
const results = await Promise.all(
pages.map(slug =>
limit(() => fetchWithRetry(`https://api.cms.com/pages/${slug}`))
)
);
p-limit 庫(2025 年每週 250 萬次 npm 下載)是我的首選。您也可以在請求之間添加延遲:
const limit = pLimit(3);
const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
const results = await Promise.all(
pages.map((slug, i) =>
limit(async () => {
if (i > 0) await delay(200); // 200ms between requests
return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
})
)
);
在 Next.js API 路由中實現速率限制
現在讓我們翻到另一面——您正在構建一個 API 並需要保護它。如果您使用 Next.js 構建,以下是如何將速率限制添加到您的 API 路由。
簡單的記憶體中速率限制器
對於單伺服器部署或開發期間,這工作得很好:
// lib/rate-limit.ts
type RateLimitEntry = {
count: number;
resetTime: number;
};
const rateLimitMap = new Map<string, RateLimitEntry>();
export function rateLimit({
windowMs = 60 * 1000,
maxRequests = 100,
}: {
windowMs?: number;
maxRequests?: number;
} = {}) {
return function check(identifier: string): {
allowed: boolean;
remaining: number;
resetIn: number;
} {
const now = Date.now();
const entry = rateLimitMap.get(identifier);
if (!entry || now > entry.resetTime) {
rateLimitMap.set(identifier, {
count: 1,
resetTime: now + windowMs,
});
return { allowed: true, remaining: maxRequests - 1, resetIn: windowMs };
}
if (entry.count >= maxRequests) {
return {
allowed: false,
remaining: 0,
resetIn: entry.resetTime - now,
};
}
entry.count++;
return {
allowed: true,
remaining: maxRequests - entry.count,
resetIn: entry.resetTime - now,
};
};
}
在 Next.js App Router API 路由中使用它:
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';
const limiter = rateLimit({ windowMs: 60_000, maxRequests: 30 });
export async function GET(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
const { allowed, remaining, resetIn } = limiter(ip);
if (!allowed) {
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil(resetIn / 1000)),
'X-RateLimit-Limit': '30',
'X-RateLimit-Remaining': '0',
},
}
);
}
// Your actual route logic here
return NextResponse.json(
{ data: 'Here you go' },
{
headers: {
'X-RateLimit-Limit': '30',
'X-RateLimit-Remaining': String(remaining),
},
}
);
}
使用 Upstash Redis 的生產速率限制
記憶體中的方法在您在 Vercel 等無伺服器平台上運行時會出現故障,因為每次函數調用可能會觸發不同的實例。您需要一個共享存儲。Upstash Redis 是 2025 年最受歡迎的選擇。
npm install @upstash/ratelimit @upstash/redis
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(30, '60 s'),
analytics: true,
prefix: 'api-ratelimit',
});
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';
export async function GET(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
const retryAfter = Math.ceil((reset - Date.now()) / 1000);
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{
status: 429,
headers: {
'Retry-After': String(retryAfter),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': String(reset),
},
}
);
}
return NextResponse.json({ data: 'Success' }, {
headers: {
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': String(remaining),
},
});
}
Upstash 的免費層給您每天 10,000 個請求,對於小型項目來說已經足夠了。他們的 Pro 計劃從 2025 年初開始每月 10 美元起,每日命令 500K。
中間件級速率限制
如果您想對所有 API 路由進行速率限制,Next.js 中間件是正確的位置:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/')) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, reset } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
},
}
);
}
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*',
};
速率限制策略比較
不是所有速率限制算法都是相等的。以下是主要算法的比較方式:
| 算法 | 它如何工作 | 優點 | 缺點 | 最適合 |
|---|---|---|---|---|
| 固定窗口 | 計算固定時間窗口中的請求(例如,每分鐘) | 易於實現 | 窗口邊界的突發可能允許 2 倍的限制 | 簡單的 API、內部工具 |
| 滑動窗口 | 計算滾動時間段內的請求 | 更平滑的分配 | 稍微更複雜、更多記憶體 | 大多數生產 API |
| 令牌桶 | 令牌以穩定的速率補充,每個請求花費一個令牌 | 允許控制的突發 | 更複雜的狀態管理 | 需要爆發容錯的 API |
| 洩漏桶 | 請求進入隊列並以固定速率處理 | 非常平滑的輸出速率 | 可能增加延遲,請求可能被丟棄 | Webhook 傳遞、作業處理 |
| 滑動窗口日誌 | 存儲每個請求的時間戳 | 最準確 | 大規模時高記憶體使用量 | 低容量、高精度需求 |
對於大多數 Web 應用程序,滑動窗口是最佳平衡點。這是 Upstash 默認使用的,除非您有特定的理由選擇其他方式,否則我會推薦它。
Astro 及其他框架中的速率限制
如果您使用 Astro 構建,速率限制的工作方式不同,因為 Astro 主要是一個靜態優先的框架。但使用 Astro 的伺服器端點(在 SSR 模式中可用),概念是相同的:
// src/pages/api/data.ts
import type { APIRoute } from 'astro';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(30, '60 s'),
});
export const GET: APIRoute = async ({ request }) => {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, reset } = await ratelimit.limit(ip);
if (!success) {
return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
status: 429,
headers: {
'Content-Type': 'application/json',
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
},
});
}
return new Response(JSON.stringify({ data: 'Hello' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};
對於在 Cloudflare Workers 上邊緣部署的應用程序,您可能還應該考慮 Cloudflare 的內置速率限制規則,該規則在基礎設施級別運行,可以處理遠超應用程序級別解決方案的流量。他們的進階速率限制在 Business 計劃上每 10,000 個良好請求的費用為 0.05 美元。
在生產環境中監控和調試 429 錯誤
您無法修復看不到的東西。以下是我處理生產環境中 429 錯誤的檢查清單:
當您收到 429s 時
- 檢查哪個 API 返回 429 一一查看響應 URL,而不是只是狀態碼
- 記錄
Retry-After標頭 一一如果它始終很長,您可能需要更高的層級計劃 - 審計您的請求模式 一一您是在進行冗餘調用嗎?您可以批量請求嗎?
- 實現快取 一一使用
stale-while-revalidate、Redis 快取或 Next.js ISR 來減少 API 呼叫 - 檢查多個環境是否共享 API 金鑰 一一這是最常見的「神秘」429 原因
當您發送 429s 時
- 設置儀表板 一一隨著時間推移追蹤 429 響應速率
- 識別頂級違規者 一一哪個 IP 地址或 API 金鑰最常觸發限制?
- 檢查您的限制 一一它們是否過於限制?太寬鬆?檢查您的伺服器容量並進行調整
- 始終發送
Retry-After一一成為一個好的 API 公民 - 包含有用的錯誤消息 一一告訴客戶端哪個限制他們觸發了,以及何時重試
精心設計的 429 響應體看起來像這樣:
{
"error": {
"type": "rate_limit_exceeded",
"message": "You've exceeded 30 requests per minute. Please wait before retrying.",
"retryAfter": 42,
"documentation": "https://docs.yourapi.com/rate-limits"
}
}
這比只是 { "error": "Too many requests" } 無限有用。
如果您在無頭架構上持續面臨速率限制問題——無論是在構建期間、運行時還是兩者——可能值得與我們聯絡以討論您的架構。我們在不同 CMS 和框架組合中看到了許多這些問題,通常有一個模式級別的修複,而不是只是對症狀進行創可貼。
常見問題
HTTP 429 Too Many Requests 是什麼意思?
HTTP 429 是一個狀態碼,表示您在給定時間段內向伺服器發送了太多請求。伺服器正在限制您的速率——它要求您放慢速度。這不是認證錯誤或伺服器錯誤;您的請求可能是有效的,只是有太多了。伺服器應該包含一個 Retry-After 標頭,告訴您何時再次嘗試。
我如何修復 429 錯誤?
如果您收到來自 API 的 429 錯誤,在您的重試邏輯中實現帶有抖動的指數退避,減少您的請求頻率,添加快取以避免冗餘調用,並尊重 Retry-After 標頭。如果您在構建期間觸發限制,使用帶有並發控制的請求隊列。如果它一致地發生,您可能需要升級到具有更寬鬆速率限制的更高 API 計劃。
Retry-After 標頭是什麼?
Retry-After 標頭與 429(或 503)響應一起發送,以告訴客戶端在進行另一個請求之前要等待多長時間。它可以指定為秒數(例如 Retry-After: 60)或 HTTP 日期(例如 Retry-After: Thu, 01 Jan 2026 00:00:00 GMT)。並非所有 API 都包含此標頭,但設計良好的 API 會。
我如何在 Next.js 中實現速率限制?
對於開發或單伺服器部署,您可以使用內存中的 Map 來追蹤每個 IP 地址的請求計數。對於在 Vercel 等平台上的生產無伺服器部署,使用 Upstash Redis 和 @upstash/ratelimit 包。您可以在單個路由級別或使用 Next.js 中間件在所有 API 路由上應用速率限制。
429 和 503 錯誤有什麼區別?
429 Too Many Requests 特別是關於速率限制一一您的客戶端發送了太多請求。503 Service Unavailable 意味著伺服器過載或維護中,無法從任何人處理任何請求。兩者都可能包含 Retry-After 標頭,但它們表示非常不同的問題。429 針對您;503 影響每個人。
速率限制可以防止 DDoS 攻擊嗎? 速率限制是針對 DDoS 攻擊的一層防禦,但它本身不足以應對。應用級別的速率限制(如您在 Next.js 中實現的)可以處理中等濫用,但嚴重的 DDoS 攻擊需要在基礎設施級別進行緩解一一使用 Cloudflare、AWS Shield 或您的託管提供商的內置保護等服務。將應用級別的速率限制視為保鏢,將基礎設施級別的保護視為堡壘牆。
我應該為我的 API 設置什麼速率限制? 這完全取決於您的使用情況。公開 API 的常見起點是每分鐘 60 個請求(每個 IP)或每小時 1,000 個請求(每個 API 金鑰)。對於經過身份驗證的用戶,您可能允許更多。關鍵是監控實際使用模式,設置適應合法使用且有一些空間的限制,並根據真實數據進行調整。開始時限制更多,然後放寬一一在用戶開始依賴更高速率後收緊限制會更困難。
為什麼我在靜態網站構建期間收到 429 錯誤?
靜態網站生成器(如 Next.js 和 Astro)在構建時為每個頁面獲取數據。如果您有數百或數千個頁面,那就是數百或數千個 API 呼叫在快速連續中。大多數 CMS API 的速率限制在每秒 5-20 個請求之間。使用 p-limit 或類似庫將並發限制在 3-5 個同時請求,在批次之間添加小延遲,並考慮使用增量構建(Next.js 中的 ISR 或 Astro 的增量內容集合)以避免一次重建所有內容。