먼저
- /etc/nginx/nginx.conf : Nginx의 메인 설정 파일이며, 글로벌 설정 및 기본 동작 방식을 설정하기 위해 사용
- /etc/nginx/sites-enabled/default : 개별 서버 블록(사이트) 설정 파일이다. 특정 사이트나 도메인에 대한 설정을 정의
이 두 파일의 차이점을 알고 가야한다.
또한 필자는 인증서를 Certbot을 통해서 발급 받았다.
메인 설정 파일 수정
sudo nano /etc/nginx/nginx.conf
http블록 안에 아래의 내용을 추가한다.(이미 정의되어 있다면 변경한다.)
# Nginx 버전정보 노출 끔
server_tokens off;
# SSL Settings
ssl_protocols TLSv1.2 TLSv1.3; # SSL/TLS 인증은 TLSv1, TLSv1.1 과 SSLv3는 보안에 취약하므로 TLSv1.2 TLSv1.3만 허용한다.
ssl_prefer_server_ciphers on; # 클라이언트가 지원하는 암호화 스위트(cipher suite) 목록 대신, 서버가 선호하는 암호화 스위트를 우선적으로 사용한다.
# DoS 공격방어
# 요청 제한 영역 정의 (클라이언트 IP 기반)
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=10r/s;
map $remote_addr $limit {
default 1;
"127.0.0.1" 0;
}
설명
SSL/TLS 인증은 TLSv1, TLSv1.1 과 SSLv3는 보안에 취약하므로 TLSv1.2 TLSv1.3만 허용한다.
클라이언트가 지원하는 암호화 스위트 목록 대신, 서버가 선호하는 암호화 스위트를 우선적으로 사용한다. -> 클라이언트의 낮은 보안 수준의 암호화 알고리즘 사용을 방지한다.
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=10r/s;
클라이언트 IP를 기준으로 요청 빈도를 제한한다.
요청 제한 정보를 저장할 req_zone이라는 이름의 10MB 메모리 영역을 생성한다.
초당 10개의 요청만 허용한다.(정확한 내용은 글의 가장 하단 글을 참고)
- $binary_remote_addr: 클라이언트 IP 주소를 압축한 바이너리 형식으로 저장하여 메모리 사용을 최적화한다.
- zone=req_zone:10m: 10MB 크기의 메모리를 할당. 이는 약 16만 개의 IP를 저장 가능.
- rate=10r/s: 초당 10개의 요청을 초과하는 경우 제한.
이렇게 설정하면 DoS(Denial of Service) 공격으로 인한 과도한 요청을 방지한다.
map $remote_addr $limit {
default 1;
"127.0.0.1" 0;
}
설명
클라이언트 IP 주소에 따라 $limit 변수를 설정하고, 127.0.0.1(로컬 호스트)은 제한에서 제외하고, 다른 IP는 기본적으로 요청 제한을 적용.
- map: 요청 처리 시 조건에 따라 변수를 동적으로 설정.
- "127.0.0.1" 0;: 로컬 호스트(서버 자체 요청)는 제한하지 않음.
- default 1;: 로컬 호스트를 제외한 모든 요청은 제한 적용.
이렇게 설정하면 서버 내부에서 발생하는 요청은 제한 없이 처리 가능 하며 외부 클라이언트에 대해서만 요청 제한이 적용된다.
개별 설정 파일 수정
sudo nano /etc/nginx/sites-enabled/default
이 파일엔 server 블록이 3개가 있다.
첫 번째 블록은 http에 대한 설정이고, 두 번째 블록은 https에 대한 설정이다.
첫 번째 블록(http)에 아래의 내용을 추가한다.
# http -> https 리다이렉트
return 301 https://$host$request_uri;
두 번째 블록(https)에 아래의 내용을 추가한다.(이미 정의되어있다면 수정)
location / {
try_files $uri $uri/ =404;
# DoS 공격 방어
limit_req zone=req_zone burst=20 nodelay;
limit_req_status 429;
}
# 보안헤더
add_header Strict-Transport-Security "max-age=86400; includeSubDomains; preload" always; # max-age 부분의 숫자는 알아서 판단
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
#add_header Content-Security-Policy "default-src 'self';"; #이건 적절히 판단
# ssl 관련
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; #인증서 위치는 알아서 수정
설명
<location / {...}>
- 기본적으로 모든 https 요청에 대해서 적용
- zone=req_zone: 요청 제한이 적용될 영역(메모리 공간).
- burst=20: 최대 20개의 초과 요청을 대기열에 허용.
- nodelay: 대기열에 요청을 추가하지 않고 초과 요청을 즉시 차단.
정상적인 요청은 초당 허용된 요청 속도로 처리되고, DoS 공격처럼 과도한 요청이 들어오면, 초과 요청은 즉시 차단한다.
보안 헤더
<Strict-Transport-Security (HSTS)>
- HTTPS만을 사용하도록 강제하는 HTTP 헤더.
- max-age=86400: 24시간(86400초) 동안 HSTS 정책 유지. -> 테스트 후 1년 이상 동안 유지하도록 변경
- includeSubDomains: 하위 도메인에도 HSTS 정책 적용.
- preload: 브라우저 HSTS Preload List에 포함 요청.
- 중간자 공격(MITM)을 방지.
- 정식으로 등록하려면 https://hstspreload.org/ 이 사이트에서 등록해야 한다.
<X-Content-Type-Options nosniff>
- 브라우저가 MIME 타입 스니핑을 하지 못하도록 설정.
- 브라우저가 잘못된 MIME 타입으로 파일을 실행하지 않도록 방지.
- Cross-Site Scripting(XSS) 공격 차단.
<X-Frame-Options SAMEORIGIN>
- iframe으로 현재 페이지를 로드할 수 있는 조건을 설정.
- SAMEORIGIN 설정으로 같은 도메인에서만 iframe으로 로드 가능.
- Clickjacking 공격 방지.
<X-XSS-Protection "1; mode=block">
- 브라우저의 XSS(교차 사이트 스크립팅) 필터를 활성화.
- XSS 공격이 감지되면 페이지 렌더링 차단.
HSTS(HTTP Strict Transport Security)는 웹 서버가 브라우저에 HTTPS 연결을 강제하도록 지시하는 보안 정책으로, 중간자 공격(MITM)을 방지하고 데이터의 무결성을 보호한다.
이를 지원하는 브라우저는 설정된 max-age기간 동안 HTTP 요청을 HTTPS로 리다이렉트한다.
HSTS Preload List는 브라우저에 미리 정의된 HSTS 적용 도메인 목록으로, https://hstspreload.org/ 에서 등록할 수 있다.
등록된 도메인은 첫 연결부터 HTTPS만 사용하도록 강제되며, 추가적인 보안이 제공된다.
ssl 설정
<ssl_stapling on;>
- OCSP(Online Certificate Status Protocol) 스테이플링 활성화.
- 서버가 클라이언트에게 SSL 인증서 상태를 전달.
- 클라이언트가 인증서 상태를 직접 확인하지 않아도 되므로 연결 속도 향상.
- 서버가 최신 인증서 상태를 클라이언트에게 제공하여 보안 강화.
<ssl_stapling_verify on;>
- OCSP 응답의 유효성을 확인.
- 신뢰할 수 없는 OCSP 응답을 방지하여 보안 강화.
<resolver 1.1.1.1 8.8.8.8 valid=300s;>
- DNS 리졸버를 설정.
- 1.1.1.1, 8.8.8.8: 클라우드플레어와 구글의 DNS 서버를 사용.
- valid=300s: DNS 응답이 300초 동안 유효.
- OCSP 확인이나 도메인 리졸빙 과정에서 DNS 서버를 명시적으로 사용.
<resolver_timeout 5s;>
- DNS 요청의 타임아웃 시간을 5초로 설정.
DNS 응답이 지연되거나 실패할 경우 연결 대기 시간을 줄임.
<ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;>
- 서버가 신뢰할 수 있는 인증서 목록을 정의.
- OCSP 스테이플링을 위해 CA 루트 인증서를 지정.
- 신뢰할 수 없는 인증서를 방지하여 SSL 연결 보안 강화.
- OCSP를 위해서 필수적
모든 설정 파일을 수정하였으면 구문 오류가 있는지 확인한다.
sudo nginx -t
Nginx를 재시작하지않고 설정을 새로고침하려면 아래의 명령어를 실행
sudo systemctl reload nginx
Nginx를 재시작하려면 아래의 명령어를 실행
sudo systemctl restart nginx
인증서 자동 갱신 설정
인증서가 만료되면 인증서를 갱신하고 nginx를 재시작 하거나 인증서를 다시 불러올 필요가 있다.
수동으로 로드하는 것은 귀찮으니 자동화 스크립트를 작성한다.(크론탭 사용)
먼저 시간대를 변경해야 한다.
UTC 기준이 아닌 KST(한국 시간대)로 변경한다.
sudo timedatectl set-timezone Asia/Seoul
크론탭을 작성한다.(sudo 명령어를 꼭 붙힐 것)
sudo crontab -e
아래의 명령어를 추가한다.
0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"
- 0 3 * * * : 매월 1일 새벽 3시 정각에
- certbot renew : 인증서 갱신(만료 기한이 30일 이내로 남아있는 경우만)
- --quiet : 오류가 발생한 경우에만 로그파일에 경고 메시지 남김
- --deploy-hook "sudo systemctl reload nginx" : nginx의 기존 연결을 끊지 않고 새로운 설정 반영(이 과정에서 갱신된 인증서가 반영됨)
마지막으로 크론탭 변경사항 저장을 한다
sudo service cron restart
기타
인증서 자동 갱신 테스트
sudo certbot renew --dry-run
이 명령어는 certbot이 자동으로 인증서가 자동 갱신되는지 테스트 결과를 제공한다.(실제로 갱신되진 않음)
인증서 만료일 확인
sudo certbot certificates
지금 당장 인증서 갱신
이 명령어를 사용하기 이전에 nginx를 중지해야한다.
sudo systemctl stop nginx
sudo certbot renew
sudo systemctl start nginx
크론탭 로그 확인
sudo cat /var/log/cron
레퍼런스
- https://wikidocs.net/223831
- https://velog.io/@moonseok/NGINX-limitreq-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0
- https://zionh.tistory.com/23
- https://yoyostudy.tistory.com/60
- https://madplay.github.io/post/a-guide-to-cron-expression
'서버 구축기' 카테고리의 다른 글
[GitHub Actions] Self-Hosted Runner (0) | 2025.01.09 |
---|---|
[Ubuntu] GeoIP 를 이용한 해외 IP SSH 접속 차단 (0) | 2025.01.02 |
[MySQL] MySQL 보안 설정 (1) | 2024.12.27 |
[Ubuntu] 외장 SSD ext4 초기화 + 마운트 (0) | 2024.12.26 |
[Ubuntu] ssh 원격접속 보안 강화 (0) | 2024.11.29 |