집 서버에서 돌리는 서비스를 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 패턴(확장자 요구 여부 등)을 반드시 함께 확인하자.