Published on

nginx 설정 파일 쉽게 하는 법: 구조부터 적용까지

Authors
  • avatar
    Name
    Indo Yoon
    Twitter

nginx 설정 파일은 main, events, http, server, location이라는 블록의 역할만 구분하면 읽는 속도가 눈에 띄게 빨라집니다. 이 글은 기본 설정 파일을 열어 본 상태에서 시작해 블록 구조를 파악하고, include로 파일을 나누고, 리버스 프록시를 붙인 뒤, 문법 검사와 무중단 재적용으로 마무리하는 순서를 다룹니다. 설정 항목은 nginx 공식 문서와 nginx 공식 관리 가이드에 나온 범위 안에서만 설명하고, 배포판마다 다른 경로는 따로 구분해 정리합니다.

한눈에 보기

nginx 설정 파일은 "블록 안에 디렉티브를 나열한다"는 규칙 하나로 읽힙니다. 디렉티브는 이름과 인자로 이루어지고 세미콜론으로 끝나며, 블록은 중괄호로 범위를 표시합니다. 주석은 샵 기호로 시작하고, 줄바꿈과 들여쓰기는 문법에 영향을 주지 않으므로 사람이 읽기 좋게 정리하면 됩니다.

가장 중요한 감각은 "어디에 쓸 수 있는 디렉티브인가"입니다. 예를 들어 worker_processes는 최상위인 main 블록에서만 쓸 수 있고, worker_connections는 events 블록 안에서만 쓸 수 있습니다. server_name은 server 블록, try_files는 location 블록이 자리입니다. 문서에서 디렉티브를 찾을 때 Context 항목을 함께 보면 이 범위가 그대로 적혀 있습니다.

설정을 바꾸는 흐름은 항상 같습니다. 파일을 수정하고, nginx -t로 문법을 검사하고, nginx -s reload나 서비스 재적용 명령으로 반영합니다. 이 순서를 지키면 오타 하나로 서비스가 내려가는 상황을 거의 피할 수 있습니다.

  • 블록: main → events → http → server → location 순으로 좁아집니다.
  • 문법: 디렉티브는 세미콜론으로 끝나고, 블록은 중괄호로 감쌉니다.
  • 분리: http 블록에서 include로 파일을 불러옵니다.
  • 검증: 반영 전에 반드시 문법 검사를 먼저 합니다.
  • 반영: 재시작보다 재적용을 먼저 고려합니다.

설정 블록 구조 비교

설정 파일을 처음 볼 때 헷갈리는 지점은 "이 줄이 어느 블록 소속인가"입니다. 아래 표처럼 블록별 역할과 대표 디렉티브를 묶어 두면 위치를 외우지 않아도 됩니다.

블록역할주로 두는 디렉티브
main프로세스 전반 설정user, worker_processes, error_log, pid
events연결 처리 방식worker_connections
httpHTTP 전반 공통 설정include, default_type, log_format, access_log, sendfile
server가상 호스트 단위listen, server_name, root, return
locationURI 단위 처리try_files, proxy_pass, alias, expires
upstream백엔드 서버 묶음server, least_conn, ip_hash

블록은 위에서 아래로 갈수록 범위가 좁아집니다. 상위 블록에 쓴 값은 하위 블록으로 상속되지만, 하위 블록에서 다시 선언하면 그 범위에서는 하위 값이 우선합니다. 이 상속 규칙 덕분에 http 블록에 공통 로그 형식을 두고 server마다 다르게 쓰는 식의 구성이 가능합니다.

설정 파일이 놓이는 위치

패키지로 설치한 경우 기본 설정 파일은 보통 /etc/nginx/nginx.conf입니다. 다만 배포판마다 가상 호스트를 나누는 관습이 다릅니다.

  • 데비안·우분투 계열은 /etc/nginx/sites-available에 파일을 두고 /etc/nginx/sites-enabled에 심볼릭 링크를 걸어 활성화합니다.
  • 레드햇 계열은 /etc/nginx/conf.d 아래에 .conf 파일을 두고 http 블록의 include로 통째로 불러옵니다.
  • 컨테이너 이미지나 소스 컴파일 설치라면 경로가 다를 수 있으므로 nginx -V 출력의 설정 경로 항목을 확인하는 편이 정확합니다.

어느 쪽이든 최종적으로는 http 블록의 include가 실제 파일을 읽습니다. 그래서 "파일을 만들었는데 적용이 안 된다"는 상황의 원인은 대부분 include 경로 밖에 파일을 둔 경우입니다.

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}

위 골격에서 눈여겨볼 부분은 include 두 줄입니다. MIME 타입 정의와 가상 호스트 파일을 각각 불러오고 있고, 새 서비스를 추가할 때는 conf.d 아래에 파일 하나만 더 두면 됩니다. 메인 파일을 직접 고치지 않으므로 되돌리기도 쉽습니다.

단계별로 설정 파일 작성하기

1. 현재 설정을 먼저 백업합니다

수정 전에 기존 파일을 다른 이름으로 복사해 둡니다. 문법 검사에서 실패했을 때 원본으로 되돌리는 것이 가장 빠른 복구 방법입니다. 배포 자동화에 설정 파일이 포함되어 있다면 저장소 이력이 곧 백업이 되므로, 어떤 변경이 반영되었는지 커밋 단위로 남기는 편이 좋습니다.

2. main과 events 블록을 확인합니다

worker_processes는 보통 auto로 두고, error_log와 pid 경로가 실제 실행 계정에서 쓰기 가능한지 확인합니다. events 블록의 worker_connections는 동시 연결 처리량과 직결되므로, 시스템의 파일 디스크립터 한도와 함께 봐야 합니다.

3. http 블록에 공통 설정을 모읍니다

로그 형식, sendfile, keepalive_timeout, client_max_body_size처럼 모든 가상 호스트에 적용할 값을 http 블록에 둡니다. 업로드가 있는 서비스라면 client_max_body_size를 기본값 그대로 두었다가 큰 파일에서 실패하는 경우가 많으므로 여기서 한 번 정하고 갑니다.

4. server 블록으로 가상 호스트를 만듭니다

listen으로 포트와 주소를, server_name으로 도메인을 지정합니다. 도메인은 공백으로 구분해 여러 개를 나열할 수 있고, 와일드카드나 정규식 형태도 지원합니다. 같은 포트에 server 블록이 여러 개 있으면 server_name이 가장 정확히 일치하는 블록이 선택되고, 일치하는 이름이 없으면 그 포트의 기본 블록이 응답합니다.

5. location 블록으로 URI를 나눕니다

정적 파일은 root와 try_files로, 백엔드 호출은 proxy_pass로 처리합니다. location 지정자는 아래 표처럼 종류가 나뉘고 우선순위가 다릅니다.

지정자의미예시
=정확히 일치location = /health { }
^~접두 일치, 정규식 검사 생략location ^~ /static/ { }
~대소문자 구분 정규식location ~ \.php$ { }
~*대소문자 무시 정규식`location ~* .(jpg
없음일반 접두 일치location /api/ { }

우선순위는 정확 일치가 가장 먼저이고, 그다음이 ^~ 접두 일치, 그다음이 정규식, 마지막이 일반 접두 일치입니다. 이 순서를 모르면 "왜 이 location이 잡히지" 하는 상황이 생기므로, 규칙을 정할 때 표를 옆에 두고 확인하는 편이 안전합니다.

6. 문법을 검사합니다

sudo nginx -t

출력에 문법 성공 메시지와 함께 설정 파일 경로, 테스트 파일 경로가 나옵니다. 실패하면 파일 이름과 줄 번호가 함께 표시되므로 그 지점만 보면 됩니다. 가장 흔한 원인은 세미콜론 누락과 중괄호 짝 불일치입니다.

7. 재적용합니다

sudo nginx -s reload
# 또는
sudo systemctl reload nginx

재적용은 새 설정을 읽은 워커를 새로 띄우고 기존 워커는 처리 중인 요청을 마친 뒤 정리하는 방식입니다. 반면 재시작은 연결이 끊길 수 있으므로, 급한 장애 상황이 아니라면 재적용을 먼저 시도합니다.

리버스 프록시 구성 예시

백엔드 애플리케이션 앞에 nginx를 두는 구성은 아래처럼 정리됩니다. 핵심은 백엔드에 원래 요청 정보를 넘기는 헤더입니다.

upstream app_backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://app_backend;
        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;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

Host를 그대로 넘기지 않으면 백엔드가 도메인 기반 라우팅이나 리다이렉트 주소를 잘못 만들 수 있습니다. X-Forwarded-Proto가 없으면 HTTPS로 들어온 요청을 백엔드가 HTTP로 착각해 무한 리다이렉트를 만드는 경우도 있습니다. proxy_pass 뒤에 슬래시를 붙이면 경로가 치환되고 붙이지 않으면 원래 URI가 그대로 전달되므로, 이 차이도 함께 기억해 두면 좋습니다.

정적 파일과 백엔드를 한 도메인에서 나누는 구성은 아래처럼 location을 갈라서 만듭니다.

server {
    listen 80;
    server_name www.example.com;
    root /var/www/site;

    location /assets/ {
        try_files $uri =404;
        expires 7d;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

try_files의 마지막 인자는 파일이 아니라 내부 리다이렉트 대상입니다. 위 예시에서는 어떤 경로로 들어와도 index.html로 넘겨서 클라이언트 라우팅을 처리하도록 한 형태입니다.

상황별 선택

상황고르는 방법이유
정적 파일만 제공root + try_files파일 시스템 경로를 그대로 매핑해 단순합니다
백엔드 한 대 뒤에 두기proxy_pass 직접 지정upstream 블록 없이도 충분합니다
백엔드 여러 대upstream + 분배 방식서버 목록을 한곳에서 관리할 수 있습니다
도메인 여러 개server 블록 분리인증서와 로그를 도메인별로 나눌 수 있습니다
HTTPS 종료listen에 ssl 지정인증서 관리를 nginx 한곳으로 모읍니다
경로 일부만 다른 서버로location 단위 proxy_pass한 도메인 안에서 라우팅을 나눌 수 있습니다

로드 밸런싱 방식은 upstream 블록에서 정합니다. 별도 지정이 없으면 라운드 로빈이고, least_conn은 연결이 적은 서버를, ip_hash는 클라이언트 주소 기준 고정 분배를 사용합니다. 세션을 서버 메모리에 두는 구조라면 ip_hash나 별도 세션 저장소를 함께 검토해야 합니다.

주의할 점

  • 세미콜론을 빠뜨리면 그다음 줄까지 이어서 해석되면서 엉뚱한 줄에서 오류가 납니다. 오류 메시지의 줄 번호보다 한 줄 위를 먼저 확인해 보세요.
  • root는 URI를 경로 뒤에 붙이고, alias는 URI 부분을 치환합니다. 둘을 섞으면 파일을 찾지 못하는 404가 반복됩니다.
  • proxy_pass에 슬래시를 붙이는지 여부로 전달 경로가 달라집니다. 백엔드 로그를 보며 실제 경로를 확인하는 편이 빠릅니다.
  • 같은 포트에 server_name이 중복되면 먼저 읽힌 블록이 이깁니다. include 순서와 파일 이름 정렬 순서가 영향을 줍니다.
  • 큰 업로드가 있는 서비스는 client_max_body_size를 확인하지 않으면 요청이 거부됩니다.
  • 재적용 전에는 항상 문법 검사를 먼저 합니다. 검사 없이 재적용하면 오류가 있는 설정이 그대로 반영되어 서비스가 멈출 수 있습니다.
  • 설정 파일과 인증서 파일의 권한을 실행 계정 기준으로 확인합니다. 읽기 권한이 없으면 시작 단계에서 실패합니다.

자주 묻는 질문

설정을 바꾼 뒤에는 꼭 재시작해야 하나요?

대부분은 재시작 없이 재적용으로 충분합니다. 재적용은 기존 연결을 처리 중인 워커가 일을 마친 뒤 교체되는 방식이라 무중단에 가깝습니다. 다만 리슨 포트 변경이나 모듈 추가처럼 프로세스 구조 자체가 바뀌는 변경은 재시작이 필요할 수 있습니다.

문법 검사는 어디서 실행하나요?

설정 파일이 있는 서버에서 nginx -t를 실행합니다. 관리자 권한이 필요한 경우가 많고, 컨테이너 환경이라면 실행 중인 컨테이너 안에서 같은 명령을 돌려 확인합니다. 검사에 성공하면 설정 파일과 테스트 파일 경로가 함께 출력됩니다.

server_name에 도메인을 여러 개 적어도 되나요?

가능합니다. 공백으로 구분해 나열하면 같은 server 블록이 여러 이름에 응답합니다. 다만 인증서나 로그를 도메인별로 분리해야 한다면 블록을 나누는 편이 관리하기 쉽습니다.

include로 파일을 나누면 순서가 중요한가요?

대부분의 경우 순서가 문제되지 않지만, 같은 포트와 같은 이름을 가진 server 블록이 여러 파일에 있으면 먼저 읽힌 쪽이 선택됩니다. 그래서 파일 이름에 번호나 서비스 이름을 붙여 정렬 순서를 예측 가능하게 만드는 편이 좋습니다.

설정 파일을 잘못 고쳐서 서비스가 내려갔을 때는 어떻게 하나요?

백업해 둔 파일로 되돌린 뒤 문법 검사를 다시 하고 재적용합니다. 배포 파이프라인에 설정 파일이 포함되어 있다면 이전 커밋으로 되돌리는 것이 가장 빠릅니다. 이후에는 반영 전 문법 검사를 자동화 단계에 넣어 두면 같은 상황을 반복하지 않습니다.

출처

설정 파일을 손보다 보면 재적용과 재시작의 차이, 무중단 배포 중 설정이 반영되는 시점이 궁금해지는 순간이 옵니다. 그 흐름은 nginx 재적용과 재시작의 차이 글에서 배포 순서와 함께 이어서 정리해 두었으니, 실제 운영 절차를 짜 볼 때 함께 참고해 보세요.