서버 구축하기 4. NGINX 설치하기& NGINX SSL, 보안설정

2025. 5. 16. 02:03·개발환경/Ubuntu

마지막 수정일:

2025년 5월 15일

참조 사이트&문헌

더보기

https://nginx.org/

 

nginx

nginx nginx ("engine x") is an HTTP web server, reverse proxy, content cache, load balancer, TCP/UDP proxy server, and mail proxy server. Originally written by Igor Sysoev and distributed under the 2-clause BSD License. Enterprise distributions, commercial

nginx.org

https://www.yes24.com/Product/Goods/116438200

 

NGINX 쿡북 - 예스24

애플리케이션의 성능, 신뢰성, 보안을 책임지는만능 웹 서버 소프트웨어 엔진엑스 제대로 활용하기엔진엑스 설치 및 사용법부터 다양한 모듈과 실전 운영 팁까지 다룬다. 엔진엑스라는 애플리

www.yes24.com

 


 

더보기

Nginx 설치

1. 패키지 목록 업데이트

sudo apt-get update

2. Nginx 설치

sudo apt install nginx -y

3. 서비스 시작 및 자동 시작 설정

sudo systemctl start nginx
sudo systemctl enable nginx

4. 방화벽 설정

sudo ufw allow 'Nginx Full'

위 명령어는 HTTP(80), HTTPS(443) 트래픽을 허용함.

5. Nginx 서비스 상태 확인

sudo systemctl status nginx

위 이미지와 같이 active(running) 상태인지 확인

Nginx 설치

1. docker-compose.yml 작성

services:
  jenkins:
    build: ./jenkins
    container_name: jenkins
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_data:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - global_network

  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    ports:
      - "9000:9000"
    volumes:
      - portainer_data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - global_network
    restart: always

  nginx:
    image: nginx:1.28
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    networks:
      - global_network
    restart: always

networks:
  global_network:
    external: true

volumes:
  jenkins_data:
  portainer_data:

이전에 작성했던 /infra/docker-compose.yml에 nginx 부분을 추가로 작성해 준다.

image버전은 작성일 기준 가장 최신 stable version인 1.28을 사용했다.

  • conf.d : nginx에 연결하는 개별 서비스별 설정파일
  • html : nginx 정적 웹소스 위치할 디렉토리 경로
  • nginx.conf : nginx 설정파일
  • port 80 :
  • port 443:

2. nginx.conf 작성

/infra/nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

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" "$http_x_forwarded_for"';

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

    sendfile        on;
    keepalive_timeout  65;

    # conf.d의 모든 conf 파일을 포함
    include /etc/nginx/conf.d/*.conf;
}

3. conf.d 작성

infra/nginx/conf.d/default.conf

server {
    listen 80;

    root /usr/share/nginx/html;
    index index.html;

}

4. index.html 작성

/infra/nginx/http/index.html

<!DOCTYPE html>
<html>
<head><title>Welcome to Nginx!</title></head>
<body><h1>It works!</h1></body>
</html>

5. docker에 띄우기

sudo ufw allow 'Nginx Full'

먼저 방화벽을 해제해준다.
위의 Nginx Full은 80과 443 port를 열어준다.

..../infra > docker compose up -d

서버 내부ip로 접속시, index.html이 뜨는것을 확인할 수 있다.

nginx를 띄우고 서버내부ip로 접속시, 위와같은 화면이 뜨면 일단 설치는 완료.

Nginx 설정

1, SSL 설정

도메인 설정

https://xn--220b31d95hq8o.xn--3e0b707e/

 

내도메인.한국 - 한글 무료 도메인 등록센터

한글 무료 도메인 내도메인.한국, 웹포워딩, DNS 등 무료 도메인 기능 제공

xn--220b31d95hq8o.xn--3e0b707e

먼저, ssl 설정을 통해 https 접속을 하기위해 도메인을 하나 구한다.
나는 위의 무료 도메인사이트에서 임시로 구했다.
ip는 서버의 외부아이피를 연결해준다.

certbot 설치

  nginx:
    image: nginx:1.28
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/html:/usr/share/nginx/html
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certbot/conf:/etc/letsencrypt  #추가됨
      - ./certbot/www:/var/www/certbot   #추가됨
    networks:
      - global_network
    restart: always

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    #entrypoint:
    #  - /bin/sh
    #  - -c
    #  - 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'

/infra/docker-compose.yml에 위와 같이 추가해준다.

이때, entrypoint는 주석처리 해둔다.(ssl 인증서 발급 후 활성화해야함)

  • entrypoint : 3개월마다 ssl 인증을 해줘야 하는데 매 자정 ssl 인증서를 갱신해주는 설정
infra/ > mkdir certbot/conf
infra/ > mkdir certbot/www

이후, infra/certbot/conf, infra/certbot/www 디렉토리를 만들어준다.

포트포워딩 설정

외부 접속을 위해 포트포워딩 설정을 통해 80번과 443번을 열어준다.

만약, 같은 port로 다른 내부ip에 연결되어 있다면 nginx를 구동하는 pc ip 우선순위를 위로 올려준다.

default.conf 설정

  • infra/nginx/nginx.conf
  • infra/nginx/conf.d/default.conf
`server 
{ 
  listen 80; 
    server_name {{서버 도메인 ex) google.com}}; 
    root /usr/share/nginx/html;
    index index.html;
    location /.well-known/acme-challenge/ {
    root /var/www/certbot; 
    }
}

certbot 인증을 위해 아까 연결한 서버 도메인과 인증 location을 넣어준다.

인증

docker compose restart nginx

먼저, default.conf 적용을 위해 nginx를 재시작해준다.

docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot --agree-tos --email {{이메일}} -d {{도메인}}

이후, 위 명령어를 통해 초기 인증서를 발급받고 docker-compose.yml의 entrypoint 주석을 해제한 뒤, 

certbot 컨테이너를 restart 해준다.

 


나중에 위의 명령어를 다시 실행할 필요는 없음.

  • 갱신테스트
 docker compose run --rm certbot renew --dry-run

위 명령어는 실제로 인증서를 갱신시키지는 않고 시뮬레이션만 한다. 아래사진처럼 나오면 정상작동

만약, 이 화면이 나온다면 docker-compose.yml의 entrypoint 주석을 해제하자

1. https 설정

server {
    listen 80;
    server_name {{도메인}};

    # HTTP 요청을 HTTPS로 리다이렉트
    location / {
        return 301 https://$host$request_uri;
    }

    # Let's Encrypt 인증 경로는 리다이렉트하지 않음
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
}

server {
    listen 443 ssl;
    server_name {{서버도메인}};

    # SSL 인증서 설정
    ssl_certificate /etc/letsencrypt/live/{{서버도메인}}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{서버도메인}}/privkey.pem;

    # SSL 설정 최적화
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

이제 인증서 구성을 다 했으니 https 접속을 위한 설정을 해준다.
/infra/nginx/conf.d/default.conf를 위와같이 작성.

이후, docker compose restart nginx을 통해 설정을 적용해주거나 portainer에서 재시작을 눌러주면 https 접속이 가능해진다.

각 요소별 설명

더보기
  • ssl_protocols TLSv1.2 TLSv1.3; : # 안전한 TLS 버전만 허용하여 오래된 취약한 프로토콜을 차단합니다
  • ssl_prefer_server_ciphers on; : # 서버가 선호하는 암호화 알고리즘을 사용하도록 설정합니다
  • ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; : # 강력한 암호화 알고리즘만 허용합니다
  • ssl_session_timeout 1d; : SSL 세션 캐시 유지 시간을 설정하여 재연결 성능을 향상시킵니다
  • ssl_session_cache shared:SSL:10m; : SSL 세션 캐시 크기를 설정하여 재연결 성능을 향상시킵니다
  • ssl_session_tickets off; : 보안 취약점이 있는 세션 티켓을 비활성화합니다
  • OCSP Stapling
  • ssl_stapling on; : OCSP 스테이플링을 활성화하여 인증서 유효성 검사 속도를 향상시킵니다
  • ssl_stapling_verify on; : OCSP 응답의 유효성을 검증합니다
  • resolver 8.8.8.8 8.8.4.4 valid=300s; : DNS 리졸버를 설정하여 OCSP 서버 조회를 가능하게 합니다
  • resolver_timeout 5s; : DNS 조회 시간 제한을 설정합니다

https 연결이 된 모습

https://www.ssllabs.com/ssltest/

 

SSL Server Test (Powered by Qualys SSL Labs)

SSL Server Test This free online service performs a deep analysis of the configuration of any SSL web server on the public Internet. Please note that the information you submit here is used only to provide you the service. We don't use the domain names or

www.ssllabs.com

위 주소를 통해 ssl 서버 테스트를 할 수 있는데, 작성시간 기준 A을 받았다.

2. 보안설정

infra/nginx.conf

user nginx;
worker_processes auto;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

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" "$http_x_forwarded_for"';

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

    # 버전 정보 숨기기
    server_tokens off;

    # 타임아웃 설정
    client_body_timeout 10;
    client_header_timeout 10;
    keepalive_timeout 65;
    send_timeout 10;

    # 버퍼 크기 제한
    client_body_buffer_size 1k;
    client_header_buffer_size 1k;
    client_max_body_size 10m;
    large_client_header_buffers 2 1k;

    # Keepalive 설정
    keepalive_requests 100;

    # Gzip 압축 설정
    gzip on;
    gzip_types text/plain application/xml application/json application/javascript text/css;
    gzip_proxied any;

    # 요청 속도 제한 설정
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # 헤더 설정
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # 외부 설정 파일 포함
    include /etc/nginx/conf.d/*.conf;



}

infra/nginx/default.conf

server {
    listen 80;
    server_name {{서버도메인}};

    # HTTP 요청을 HTTPS로 리다이렉트
    location / {
        return 301 https://$host$request_uri;
    }

    # Let's Encrypt 인증 경로는 리다이렉트하지 않음
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
}

server {
    listen 443 ssl http2;
    server_name {{서버도메인}};

    # SSL 인증서 설정
    ssl_certificate /etc/letsencrypt/live/{{서버도메인}}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{서버도메인}}/privkey.pem;

    # SSL 설정 최적화
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # OCSP Stapling
    # ssl_stapling on;
    # ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # 보안 헤더

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; connect-src 'self';" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # 요청 속도 제한 적용
    limit_req zone=mylimit burst=20 nodelay;
    limit_conn conn_limit 10;

    # 불필요한 HTTP 메소드 제한
    if ($request_method !~ ^(GET|HEAD|POST)$) {
        return 405;
    }

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    # 민감한 파일 접근 차단
    location ~ /\.(git|svn|htaccess|env|config) {
        deny all;
        return 404;
    }

    location ~ \.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist|old|orig|backup)$ {
        deny all;
        return 404;
    }

    # 오류 페이지 설정
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
  • server_tokens off : 페이지 헤더에 nginx 버전이 노출되는것을 막음

Headers의 Server에 nginx 버전이 노출된 것을 볼 수 있다.
server_tokens off; 설정 후 사진. 버전이 사라졌다.

각 요소별 설명

nginx.conf

더보기

server_tokens off;: 페이지 헤더에 nginx 버전이 노출되는 것을 막아 보안 취약점 노출 방지
client_body_timeout 10;: 클라이언트가 요청 본문을 10초 내에 전송하지 않으면 연결 종료하여 느린 공격 방지
client_header_timeout 10;: 클라이언트가 요청 헤더를 10초 내에 전송하지 않으면 연결 종료
keepalive_timeout 65;: 클라이언트와의 연결을 최대 65초까지 유지하여 리소스 효율성 향상
send_timeout 10;: 클라이언트에게 응답 전송 시 10초 내에 데이터 수신이 없으면 연결 종료
client_body_buffer_size 1k;: 클라이언트 요청 본문 버퍼 크기를 1KB로 제한하여 메모리 사용량 제어
client_header_buffer_size 1k;: 클라이언트 요청 헤더 버퍼 크기를 1KB로 제한
client_max_body_size 10m;: 클라이언트 요청 본문 최대 크기를 10MB로 제한하여 대용량 업로드 공격 방지
large_client_header_buffers 2 1k;: 대형 요청 헤더 처리를 위해 1KB 크기의 버퍼 2개 할당
keepalive_requests 100;: 하나의 연결에서 처리할 최대 요청 수를 100개로 제한
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;: IP당 초당 요청 수를 10개로 제한하여 DDoS 공격 방지
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;: IP당 동시 연결 수 제한을 위한 영역 정의
add_header X-XSS-Protection "1; mode=block" always;: 브라우저의 XSS 필터를 활성화하여 크로스 사이트 스크립팅 공격 방지
add_header X-Content-Type-Options "nosniff" always;: MIME 타입 스니핑을 방지하여 콘텐츠 보안 강화
add_header Referrer-Policy "strict-origin-when-cross-origin" always;: 리퍼러 정보 전송을 제한하여 개인정보 보호
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;: 민감한 브라우저 기능 사용을 제한

default.conf

더보기

listen 443 ssl http2;: HTTP/2 프로토콜을 활성화하여 성능 향상
return 301 https://$host$request_uri;: 모든 HTTP 요청을 HTTPS로 리다이렉트하여 안전한 연결 강제
ssl_protocols TLSv1.2 TLSv1.3;: 안전한 TLS 버전만 허용하여 취약한 프로토콜 차단
ssl_prefer_server_ciphers on;: 서버가 선호하는 암호화 알고리즘을 사용하도록 설정
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:...';: 강력한 암호화 알고리즘만 허용하여 보안 강화
ssl_session_timeout 1d;: SSL 세션 캐시 유지 시간을 1일로 설정하여 재연결 성능 향상
ssl_session_cache shared:SSL:10m;: SSL 세션 캐시 크기를 10MB로 설정하여 재연결 성능 향상
ssl_session_tickets off;: 보안 취약점이 있는 세션 티켓 기능 비활성화
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";: HSTS 설정으로 브라우저가 항상 HTTPS 사용하도록 강제
add_header X-Frame-Options SAMEORIGIN;: 페이지가 동일 출처의 프레임에서만 표시되도록 허용하여 클릭재킹 공격 방지
add_header Content-Security-Policy "default-src 'self'...";: 콘텐츠 보안 정책을 설정하여 XSS 및 데이터 주입 공격 방지
limit_req zone=mylimit burst=20 nodelay;: 요청 속도 제한을 적용하여 DDoS 공격 방지
limit_conn conn_limit 10;: IP당 최대 10개의 동시 연결만 허용하여 자원 남용 방지
if ($request_method !~ ^(GET|HEAD|POST)$) { return 405; }: GET, HEAD, POST 외의 HTTP 메소드를 차단하여 공격 표면 축소
location ~ /\.(git|svn|htaccess|env|config) { deny all; return 404; }: 민감한 설정 파일이나 버전 관리 디렉토리 접근 차단
location ~ \.(bak|config|sql|...) { deny all; return 404; }: 백업 파일이나 설정 파일 접근 차단

위의 설정으로 A+ 등급을 받았다.

보안 설정을 어느정도 마친뒤, A+ 등급을 받았다.

보안conf는 취향이나 서비스 특성 따라 따로 보안설정.conf를 만든 뒤 include 해도 무방하다.

 

다음시간엔 테스트 서버를 github에 올리고, jenkins를 통해 blue/green 배포하는 과정을 진행해 보겠다.


틀린 부분이 있거나 인용한 부분에 대해 문제가 있을 시
댓글로 알려주시면 감사하겠습니다.

'개발환경 > Ubuntu' 카테고리의 다른 글

서버 구축하기 5. Jenkins Blue/Green 무중단 배포  (0) 2025.06.16
서버 구축하기 3. docker, docker-compose, Jenkins, Portainer 설치  (0) 2025.05.08
서버 구축하기 2. CI/CD 파이프라인 구성  (0) 2025.04.30
서버 세팅스토리 1. 서버설치  (0) 2025.04.28
서버 구축하기 0. 서버선정하기  (0) 2025.04.11
'개발환경/Ubuntu' 카테고리의 다른 글
  • 서버 구축하기 5. Jenkins Blue/Green 무중단 배포
  • 서버 구축하기 3. docker, docker-compose, Jenkins, Portainer 설치
  • 서버 구축하기 2. CI/CD 파이프라인 구성
  • 서버 세팅스토리 1. 서버설치
wwwjong
wwwjong
wwwjong 님의 블로그 입니다.
  • wwwjong
    wwwjong 님의 블로그
    wwwjong
  • 전체
    오늘
    어제
    • 분류 전체보기 (16)
      • Programming (2)
        • 트러블슈팅 (2)
      • CS (1)
        • Infra (1)
      • 개발환경 (6)
        • Ubuntu (6)
      • Algorithms (6)
        • 백준 (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    docker compose
    blue/green 무중단 배포
    백준 1219
    오민식의 고민
    1219 파이썬
    ubuntu server
    백준
    Nginx
    백준 1219 파이썬
    Jenkins
    Server
    docker
    Python
    백준1219
    파이썬 1219
    ubuntu
    1219번
    파이썬 3020
    백준 1219번
    젠킨스 blue/green
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
wwwjong
서버 구축하기 4. NGINX 설치하기& NGINX SSL, 보안설정
상단으로

티스토리툴바