![[Nginx] Nginx 기본 보안 설정](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWqiUq%2FbtsLBW4xDlM%2FKZxqQ99cdjKlhe7sOysL31%2Fimg.png)
먼저
- /etc/nginx/nginx.conf : Nginx의 메인 설정 파일이며, 전역 설정 및 기본 동작 방식을 설정하기 위해 사용
- /etc/nginx/sites-available/ : 모든 사이트 설정 파일을 보관하는 디렉터리, 특정 사이트나 도메인에 대한 설정을 정의
- /etc/nginx/sites-enabled/ : 실제 Nginx가 읽어서 운영에 반영하는 설정 파일 위치
sites-enabled
- sites-enabled에 심볼릭 링크를 생성함으로써 nginx가 읽는다
sudo ln -s /etc/nginx/sites-available/도메인명 /etc/nginx/sites-enabled/
- /etc/nginx/sites-available/ 에 있는 도메인중 원하는 도메인만 여기에 심볼릭 링크를 생성한다.
- 이 심볼릭이 없다면 nginx는 해당 도메인을 서빙하지 않는다.
- 따라서 운영상 특정 도메인을 보여주고 싶지않다면 이 경로의 심볼릭 링크를 제거한다.
이 세 파일 또는 디렉터리의 차이점을 알아야 한다.
또한 필자는 인증서를 Certbot을 통해서 발급 받았다.
이 글에서는 geoIP를 통한 해외 아이피 차단까지 다루니까 아래의 명령어를 통해 추가적으로 다운로드 받아야한다.
sudo apt install libnginx-mod-http-geoip2
# 다운로드 디렉토리 생성
sudo mkdir -p /etc/nginx/geoip
# GeoLite2 데이터베이스 다운로드 (무료 버전)
cd /etc/nginx/geoip
sudo wget https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=[라이센스 키]&suffix=tar.gz
# 압축 해제
sudo tar -xzf GeoLite2-Country.tar.gz
sudo mv GeoLite2-Country_*/GeoLite2-Country.mmdb ./
메인 설정 파일 수정
sudo nano /etc/nginx/nginx.conf
http블록 안에 아래의 내용을 추가한다.(이미 정의되어 있다면 변경한다.)
user www-data; worker_processes auto; pid /run/nginx.pid; error_log /var/log/nginx/error.log; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 4098; } http { # GeoIP2 모듈 설정 geoip2 [geoip 데이터베이스 경로]/GeoLite2-Country.mmdb { $geoip2_data_country_code country iso_code; $geoip2_data_country_name country names en; } # 허용 국가 맵 설정 map $geoip2_data_country_code $allowed_country { default no; KR yes; # 한국만 허용 "" yes; # 나라 정보가 없는경우(로컬 네트워크)는 허용 } # 차단된 국가 로깅을 위한 변수 map $allowed_country $geo_log { no 1; default 0; } # 로깅 포맷 설정 log_format geoip2 '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' 'Country: $geoip2_data_country_code($geoip2_data_country_name)'; # Basic Settings sendfile on; tcp_nopush on; types_hash_max_size 2048; server_tokens off; autoindex off; include /etc/nginx/mime.types; default_type application/octet-stream; # DoS 공격방어 # 요청 제한 영역 정의 (클라이언트 IP 기반) limit_req_zone $binary_remote_addr zone=req_zone:10m rate=3r/s; map $remote_addr $limit { default 1; "127.0.0.1" 0; } #보안 헤더 add_header Strict-Transport-Security "max-age=157680000; includeSubDomains; preload" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header Content-Security-Policy "default-src 'self';" always; # SSL Settings resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s; include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot # Logging Settings access_log /var/log/nginx/access.log; # Gzip Settings gzip on; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } |
설명
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;: 로컬 호스트를 제외한 모든 요청은 제한 적용.
이렇게 설정하면 서버 내부에서 발생하는 요청은 제한 없이 처리 가능 하며 외부 클라이언트에 대해서만 요청 제한이 적용된다.
SSL 전역 설정 파일 생성
sudo nano /etc/nginx/snippets/[ssl 전역 파일명].conf
ssl_certificate /etc/letsencrypt/live/[도메인명]/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/[도메인명]/privkey.pem; |
사이트가 여러개일 경우 SSL관련해서 중복된 코드가 있기 때문에 하나의 파일에서 관리하기 위해 생성하는 파일이다.
개별 설정 파일에서 이 파일을 인클루드해서 사용한다.
개별 설정 파일 수정
- /etc/nginx/sites-available/ : 모든 사이트 설정 파일을 보관하는 디렉터리, 특정 사이트나 도메인에 대한 설정을 정의
cd /etc/nginx/sites-available/
sudo nano /etc/nginx/sites-available/[도메인명]
server { listen 80; listen [::]:80; server_name [도메인명]; # http -> https 리다이렉트 return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2 ipv6only=on; server_name [도메인명]; # 차단된 요청에 대한 조건부 로그 access_log /var/log/nginx/geo_blocked.log geoip2 if=$geo_log; # 국가 필터링 적용 if ($allowed_country = no) { return 403 '{"success":false,"message":"Access denied: Your country is not allowed"}'; } # ssl 관련 include include snippets/[ssl 전역 파일명].conf; # Certbot 인증 경로 처리 location /.well-known/acme-challenge/ { root /var/www/html; } # 리버스 프록시 location / { proxy_pass http://127.0.0.1:10000; 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; # DoS 공격 방어 limit_req zone=req_zone burst=10 delay=5; limit_req_status 429; } } |
HSTS(HTTP Strict Transport Security)는 웹 서버가 브라우저에 HTTPS 연결을 강제하도록 지시하는 보안 정책으로, 중간자 공격(MITM)을 방지하고 데이터의 무결성을 보호한다.
이를 지원하는 브라우저는 설정된 max-age기간 동안 HTTP 요청을 HTTPS로 리다이렉트한다.
HSTS Preload List는 브라우저에 미리 정의된 HSTS 적용 도메인 목록으로, https://hstspreload.org/ 에서 등록할 수 있다.
등록된 도메인은 첫 연결부터 HTTPS만 사용하도록 강제되며, 추가적인 보안이 제공된다.
이제 /etc/nginx/sites-enabled/ 이 경로에 심볼릭 링크를 추가해야한다.
- 심볼릭 링크 추가 : sudo ln -s /etc/nginx/sites-available/도메인명 /etc/nginx/sites-enabled/
- 심볼릭 링크 삭제 : sudo rm /etc/nginx/sites-enabled/도메인명
모든 설정 파일을 수정하였으면 구문 오류가 있는지 확인한다.
sudo nginx -t
Nginx를 재시작하지않고 설정을 새로고침하려면 아래의 명령어를 실행
sudo systemctl reload nginx
Nginx를 재시작하려면 아래의 명령어를 실행
sudo systemctl restart nginx
nginx에서 차단한 해외 아이피 목록 확인은 아래의 명령어로 확인 가능하다
sudo tail -n 100 /var/log/nginx/geo_blocked.log
인증서 자동 갱신 설정
인증서가 만료되면 인증서를 갱신하고 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 |