nginx sites-enabled에서 conf.d로 옮겼더니 서비스가 죽었다 (ERR_CONNECTION_REFUSED 삽질기)

집 서버에서 돌리는 서비스를 nginx 리버스 프록시로 외부에 노출해서 쓰고 있었다.

server {
    listen 80;
    server_name example.com;
    return 301 https://$host:443$request_uri;
}
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    location / {
        proxy_pass http://internal-service:9091;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

그런데 어느 날부터 접속하면 이런 에러가 떴다.

사이트에 연결할 수 없음
연결을 거부했습니다.
ERR_CONNECTION_REFUSED

TLS 핸드셰이크는커녕 포트 자체에 연결이 안 되는 상황. 인증서 문제가 아니라 더 근본적인 곳이 막혀 있다는 신호였다.

1차 점검: nginx는 멀쩡해 보이는데?

가장 먼저 의심한 건 nginx 프로세스 자체였다.

sudo nginx -t
sudo systemctl status nginx

결과는 둘 다 정상. 설정 파일 문법도 문제없고, 서비스도 active (running) 상태였다. 프록시 대상이 되는 백엔드 서비스도 로컬에서 직접 찔러보니 응답은 왔다.

curl -I http://internal-service:9091/
# HTTP/1.1 501 Not Implemented

501은 해당 애플리케이션이 HEAD 메서드를 지원하지 않아서 나는 정상 반응이라 서버 자체는 살아있다는 뜻이었다. 그럼 대체 어디가 문제일까.

결정적 단서: 포트가 아예 리슨되지 않고 있다

sudo ss -tlnp | grep -E '80|443'

원하는 포트 결과가 아무것도 없었다. nginx 프로세스는 떠 있는데 정작 해당 포트를 리슨하지 않고 있던 것이다. nginx -t가 성공한다고 해서 원하는 server 블록이 실제로 로드됐다는 보장은 없다는 걸 여기서 다시 확인했다.

sudo nginx -T 2>/dev/null | grep -A 3 "listen 443"

이것도 결과 없음. 즉 이 설정 파일 자체가 nginx에 아예 로드되지 않고 있었다.

원인: conf.d는 *.conf 확장자만 읽는다

여기서 최근에 설정 파일들을 sites-enabled에서 conf.d로 옮겼다는 게 기억났다. nginx.conf의 include 지시어를 다시 들여다봤다.

include /etc/nginx/conf.d/*.conf;      # *.conf 패턴 → 확장자 필수
include /etc/nginx/sites-enabled/*;    # * 패턴 → 확장자 무관

범인은 바로 이 한 줄 차이였다. conf.d는 glob 패턴이 *.conf로 명시돼 있어서 .conf로 끝나는 파일만 읽어들인다. 반면 sites-enabled는 패턴이 그냥 *라서 확장자와 상관없이 디렉토리 안의 파일을 전부 포함시킨다.

sites-enabled 시절 습관대로 확장자 없이 옮겨둔 설정 파일들은 conf.d에서는 조용히 무시되고 있었던 것이다. nginx -t가 계속 성공으로 뜬 이유도 애초에 이 파일들을 nginx가 읽지도 않았기 때문이었다.

$ ls -la /etc/nginx/conf.d/
-rw-r--r--  1 root root  2505  service-a
-rw-r--r--  1 root root  2615  service-b
-rw-r--r--  1 root root 11488  service-c

파일명에 확장자가 없는 게 눈에 들어왔다.

해결

파일명에 .conf만 붙여주면 끝이다.

cd /etc/nginx/conf.d
sudo mv service-a service-a.conf
sudo mv service-b service-b.conf
sudo mv service-c service-c.conf
sudo nginx -t && sudo systemctl reload nginx

이후 다시 확인하면 원하는 포트가 정상적으로 리슨 상태로 잡힌다.

왜 sites-enabled에서는 확장자가 없어도 괜찮았을까

Debian/Ubuntu 계열 nginx 패키지의 관례 차이다.

  • sites-available / sites-enabled: 심볼릭 링크로 사이트를 켜고 끄는 용도라 파일명 규칙이 자유롭다. include 패턴도 그냥 *.
  • conf.d: 보통 모듈형 설정 조각들을 모아두는 용도라 *.conf 확장자 관례를 강제하는 경우가 많다.

즉 두 디렉토리는 겉보기엔 비슷해 보여도 include 패턴 자체가 다르기 때문에, 폴더만 옮기고 파일명 관례를 그대로 유지하면 이렇게 조용히 무시당하는 문제가 생길 수 있다.

교훈

  • nginx -t 성공 ≠ 원하는 설정이 실제로 로드됨. nginx -T | grep "listen ..."로 실제 반영 여부를 확인하는 습관이 필요하다.
  • ss -tlnp로 포트가 실제로 열려 있는지 먼저 확인하면 원인 범위를 훨씬 빨리 좁힐 수 있다.
  • 설정 파일 위치를 옮길 때는 그 디렉토리의 include 패턴(확장자 요구 여부 등)을 반드시 함께 확인하자.