504 Gateway Timeout
Server Error - Upstream server didn't respond in time
HTTP 504 Gateway Timeout
What It Means
The HTTP 504 Gateway Timeout error occurs when a server acting as a gateway or proxy did not receive a timely response from the upstream server. The upstream is alive but too slow.
Request Flow
User → Nginx (proxy) → App Server ⏱️ (too slow) → 504
504 vs 408 vs 502
| Code | Who timed out |
|---|---|
| 408 | Client was too slow sending request |
| 502 | Upstream returned invalid response |
| 504 | Upstream took too long to respond |
Common Causes
- Slow database queries: Long-running queries blocking responses
- Heavy computation: Request triggers expensive processing
- External API slow: Third-party service taking too long
- Resource contention: Server under heavy load
- Network issues: Slow connection to upstream
- Deadlock: Application stuck waiting
Timeout Configuration
Nginx
location / {
proxy_pass http://app;
# Connection timeout (establishing connection)
proxy_connect_timeout 60s;
# Time to receive response
proxy_read_timeout 60s;
# Time to send request
proxy_send_timeout 60s;
}
AWS ALB / CloudFront
# ALB: idle_timeout.timeout_seconds (default 60)
# CloudFront: Origin response timeout (default 30s, max 60s)
How to Debug
# Check app response time directly
time curl http://localhost:3000/slow-endpoint
# Monitor slow queries
EXPLAIN ANALYZE SELECT ...;
# Check for resource exhaustion
top
iostat
netstat -an | grep ESTABLISHED | wc -l
# Application profiling
console.time('operation');
await heavyOperation();
console.timeEnd('operation');
Solutions
- Increase timeout: Short-term fix if requests legitimately take long
- Optimize queries: Add indexes, rewrite slow queries
- Add caching: Cache expensive computations or API calls
- Background jobs: Move slow work to async queues
- Pagination: Return smaller result sets
- Scale up: Add more servers or resources
Long-Running Request Pattern
// Instead of waiting for slow operation
// Return immediately and poll for result
app.post('/api/reports', async (req, res) => {
const jobId = await queue.add('generateReport', req.body);
res.status(202).json({
status: 'processing',
jobId,
checkUrl: `/api/reports/status/${jobId}`
});
});
app.get('/api/reports/status/:id', async (req, res) => {
const job = await queue.getJob(req.params.id);
if (job.isCompleted()) {
res.json({ status: 'complete', result: job.result });
} else {
res.json({ status: 'processing' });
}
});