레이블이 백엔드인 게시물을 표시합니다. 모든 게시물 표시
레이블이 백엔드인 게시물을 표시합니다. 모든 게시물 표시

금요일

Nginx 502 Bad Gateway 해결 — upstream 에러 원인별 완전 정리

🔍 검색 키워드: nginx 502 bad gateway 해결, nginx upstream 에러, nginx 리버스 프록시 502, nginx proxy_read_timeout, upstream sent too big header

Nginx62�는 리버스 프록시로 쓰다 보면 피할 수 없는 에러가 502다.

502 Bad Gateway
nginx/1.x.x

사용자한테 이게 보이는 순간 장애다. 빠르게 원인 찾고 고쳐야 하는데, 502는 원인이 한두 가지가 아니다. 이 글에서 발생 빈도 높은 원인과 해결책을 순서대로 정리한다.


1. 502 Bad Gateway는 뭔가

Nginx가 업스트림 서버(백엔드 앱, WAS, API 서버)에 요청을 넘겼는데 제대로 된 응답을 못 받았을 때 클라이언트에게 돌려주는 응답이다.

즉, Nginx 자체의 문제가 아니라 Nginx 뒤에 있는 서버의 문제다.


2. 원인별 분류 및 해결

원인 1: 업스트림 서버가 꺼져 있다 (가장 흔함)

에러 로그:

2026/06/26 10:00:00 [error] 12345#0: *1 connect() failed (111: Connection refused)
while connecting to upstream, upstream: "http://127.0.0.1:8080/api/health"

Connection refused가 보이면 백엔드 앱이 죽은 거다.

# 프로세스 확인
ps aux | grep java
ps aux | grep node
ps aux | grep gunicorn

# 포트 리스닝 확인
ss -tlnp | grep 8080

프로세스가 없으면 앱을 재시작한다. 재시작 후에도 바로 죽는다면 앱 자체의 에러 로그를 봐야 한다.

원인 2: 타임아웃

에러 로그:

upstream timed out (110: Operation timed out) while reading response header from upstream

백엔드가 살아있긴 한데 응답이 너무 느린 경우다. Nginx의 기본 타임아웃값(60초)을 넘기면 이 에러가 발생한다.

# nginx.conf 또는 서버 블록
location / {
    proxy_pass http://localhost:8080;

    proxy_connect_timeout 60s;   # 연결 타임아웃
    proxy_send_timeout    60s;   # 요청 전송 타임아웃
    proxy_read_timeout    120s;  # 응답 수신 타임아웃 (이걸 늘리면 됨)
}

배치 작업이나 파일 업로드처럼 처리 시간이 긴 요청은 proxy_read_timeout을 요청 성격에 맞게 올린다. 무작정 크게 올리는 건 좋지 않고, 실제 처리 시간 + 여유분 정도로 설정한다.

원인 3: 업스트림 헤더가 너무 크다

에러 로그:

upstream sent too big header while reading response header from upstream

백엔드 응답의 HTTP 헤더가 Nginx 버퍼 크기를 초과한 경우다. 쿠키가 많거나 커스텀 헤더를 대량으로 쓰는 앱에서 발생한다.

location / {
    proxy_pass http://localhost:8080;

    proxy_buffer_size          128k;
    proxy_buffers              4 256k;
    proxy_busy_buffers_size    256k;
}

헤더 크기를 줄이는 게 근본 해결이지만, 당장 급하면 버퍼를 올린다.

원인 4: Unix 소켓 권한 문제

PHP-FPM이나 Gunicorn을 소켓으로 연결할 때 발생한다.

connect() to unix:/var/run/php-fpm.sock failed (13: Permission denied)
# 소켓 파일 권한 확인
ls -la /var/run/php-fpm.sock

# 소켓 파일 소유권 변경
chown www-data:www-data /var/run/php-fpm.sock
chmod 660 /var/run/php-fpm.sock
; /etc/php-fpm.d/www.conf
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

3. 빠른 진단 순서

# 1. Nginx 에러 로그 확인 (제일 먼저)
tail -f /var/log/nginx/error.log

# 2. 업스트림 서버 상태 확인
curl -I http://127.0.0.1:8080/

# 3. 포트 리스닝 확인
ss -tlnp | grep 8080

# 4. Nginx 설정 문법 검사
nginx -t

# 5. Nginx 재시작 (설정 변경 후)
systemctl reload nginx

에러 로그에 Connection refused, timed out, Permission denied, too big header 중 뭐가 찍혀 있는지 먼저 보는 게 핵심이다.


4. 원인별 체크리스트

에러 로그 키워드 원인 해결 방법
Connection refused 업스트림 프로세스 다운 앱 재시작
timed out 응답 지연 proxy_read_timeout 증가
too big header 헤더 버퍼 부족 proxy_buffer_size 증가
Permission denied 소켓 권한 문제 소켓 파일 권한 수정
no live upstreams 업스트림 그룹 전체 다운 업스트림 서버 상태 확인

5. 실무 팁 — health check 설정

업스트림이 다운됐을 때 Nginx가 알아서 fallback하도록 설정할 수 있다.

upstream backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8081 backup;  # 메인이 죽으면 이걸로
}

max_fails는 해당 서버를 비활성화하기 전 실패 허용 횟수, fail_timeout은 비활성화 유지 시간이다.


정리

Nginx 502는 반드시 /var/log/nginx/error.log를 먼저 봐야 한다. 에러 메시지가 원인을 정확히 알려준다. Connection refused면 앱 프로세스 확인, timed out이면 타임아웃 설정, too big header면 버퍼 설정 — 이 세 가지가 전체 502의 대부분을 차지한다.

작성일: 2026-06-26

Redis WRONGTYPE Operation 해결 — 키 타입 충돌 완벽 정리

월요일

Redis 연결 에러 완전 정복: ECONNREFUSED 127.0.0.1:6379 트러블슈팅

🔍 검색 키워드: redis connection refused, redis ECONNREFUSED 6379, redis 연결 안됨, ioredis 연결 에러, spring boot redis 연결 실패, node redis ECONNREFUSED, docker redis 연결 에러

Redis 붙이다가 처음 보는 에러 아니다. 누구나 한 번쯤은 밟는다.

Error: connect ECONNREFUSED 127.0.0.1:6379

이거 뜨면 일단 당황하지 말고 순서대로 확인하면 금방 해결된다. 원인은 대부분 세 가지 중 하나다.


에러가 뜨는 주요 상황

상황에러 메시지
Node.js (ioredis)[ioredis] Unhandled error event: Error: connect ECONNREFUSED 127.0.0.1:6379
Node.js (node-redis)Error: Redis connection to 127.0.0.1:6379 failed - connect ECONNREFUSED
Spring BootUnable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException
Python (redis-py)redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379.
Docker 컨테이너 간connect ECONNREFUSED 127.0.0.1:6379 (컨테이너 내부에서 호스트 Redis 접근 시도)

레벨 1: 입문자 — Redis가 켜져 있나요?

가장 흔한 원인. Redis 서버가 꺼져 있으면 당연히 붙을 수 없다.

Redis 실행 상태 확인

# 프로세스 확인
ps aux | grep redis-server

# 포트 리스닝 확인
ss -tlnp | grep 6379

# systemd 기반 (Ubuntu/CentOS)
sudo systemctl status redis

Redis 직접 연결 테스트

redis-cli ping
# 정상이면: PONG
# 실패하면: Could not connect to Redis at 127.0.0.1:6379: Connection refused

Redis 실행 방법

# systemd로 시작
sudo systemctl start redis
sudo systemctl enable redis   # 부팅 시 자동 시작

# 백그라운드 실행
redis-server --daemonize yes

레벨 2: 실무자 — 포트/바인딩/방화벽 확인

Redis는 켜져 있는데 연결이 안 되면 이쪽을 본다.

bind 설정 문제

/etc/redis/redis.conf 기본 설정:

# 기본값: 127.0.0.1만 허용 (로컬호스트 전용)
bind 127.0.0.1

# 외부 접속 허용하려면 (주의: 보안 설정 필수)
bind 0.0.0.0

# 변경 후 재시작
sudo systemctl restart redis

방화벽 확인

# UFW (Ubuntu)
sudo ufw status
sudo ufw allow 6379

# 외부에서 포트 테스트
nc -zv <서버IP> 6379

requirepass 설정 시 인증 필요

redis-cli -a yourpassword ping

# 또는 연결 후 AUTH
redis-cli
> AUTH yourpassword
> PING

레벨 3: 고급 — Docker, Kubernetes 환경

이게 제일 헷갈린다. 컨테이너 안에서 127.0.0.1:6379컨테이너 자신을 가리킨다.

Docker Compose로 Redis 연결

# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    environment:
      - REDIS_HOST=redis      # 127.0.0.1이 아니라 서비스명!
      - REDIS_PORT=6379
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
// Node.js - 잘못된 예
const redis = new Redis({ host: '127.0.0.1', port: 6379 });

// 올바른 예
const redis = new Redis({
  host: process.env.REDIS_HOST || 'redis',
  port: parseInt(process.env.REDIS_PORT || '6379'),
});

호스트 머신의 Redis에 접근하는 경우

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - REDIS_HOST=host.docker.internal

언어별 연결 코드 및 에러 핸들링

Node.js — ioredis

const Redis = require('ioredis');

const redis = new Redis({
  host: process.env.REDIS_HOST || '127.0.0.1',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  password: process.env.REDIS_PASSWORD || undefined,
  retryStrategy(times) {
    return Math.min(times * 50, 2000);
  },
  maxRetriesPerRequest: 3,
});

redis.on('error', (err) => console.error('Redis 연결 에러:', err.message));
redis.on('connect', () => console.log('Redis 연결 성공'));

Python — redis-py

import redis, os

r = redis.Redis(
    host=os.getenv('REDIS_HOST', '127.0.0.1'),
    port=int(os.getenv('REDIS_PORT', 6379)),
    password=os.getenv('REDIS_PASSWORD'),
    decode_responses=True,
    socket_connect_timeout=5,
    retry_on_timeout=True,
)

try:
    r.ping()
    print("Redis 연결 성공")
except redis.exceptions.ConnectionError as e:
    print(f"Redis 연결 실패: {e}")

Spring Boot — application.yml

spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      timeout: 5000ms
      lettuce:
        pool:
          max-active: 10
          max-idle: 10
          min-idle: 2

원인별 체크리스트

체크 항목확인 방법조치
Redis 서버 실행 중?ps aux | grep redissystemctl start redis
포트 리스닝 중?ss -tlnp | grep 6379포트 충돌 확인
bind 설정 맞음?redis.conf 확인bind 0.0.0.0
방화벽 열려 있음?ufw status포트 허용
Docker 환경?컨테이너 여부 확인서비스명으로 host 변경
인증 필요?requirepass 설정 확인password 파라미터 추가
TLS 사용 중?Redis 6.0+ 설정 확인tls:// 스킴 및 인증서 설정
원격 서버?네트워크 경로 확인VPN, 보안그룹 확인

자주 하는 실수 TOP 3

1. Docker에서 localhost 씀
컨테이너 안에서 localhost는 컨테이너 자신이다. 다른 컨테이너의 Redis에 붙으려면 서비스명을 써야 한다.

2. 환경변수 안 쓰고 하드코딩
로컬에서 127.0.0.1로 하드코딩해두고 스테이징/프로덕션에 그대로 올리면 터진다. 처음부터 환경변수로 빼두자.

3. Redis 안 뜨고 앱부터 뜸
depends_on은 컨테이너 시작 순서만 보장하지 Redis 준비를 보장하지 않는다. healthcheck를 써야 한다.

redis:
  image: redis:7-alpine
  healthcheck:
    test: ["CMD", "redis-cli", "ping"]
    interval: 10s
    timeout: 5s
    retries: 5

app:
  depends_on:
    redis:
      condition: service_healthy

마무리

Redis 연결 에러의 90%는 위 체크리스트로 해결된다. 나머지 10%는 TLS, 클러스터 모드, Sentinel 설정 같은 고급 주제인데 그건 따로 다루겠다.

에러 메시지를 봤을 때 "Redis가 켜져 있냐 → 주소/포트가 맞냐 → 네트워크가 열려 있냐" 이 순서로만 확인해도 대부분 잡힌다.