오라클 클라우드 ARM 인스턴스에 nginx + Let’s Encrypt 인증서 적용기 (feat. 삽질 기록)

Oracle Cloud Infrastructure(OCI)의 Always Free ARM(Ampere) 인스턴스에 nginx 리버스 프록시를 올리고, Let’s Encrypt SSL 인증서를 발급하는 과정에서 예상보다 많은 삽질을 했습니다. 같은 문제를 겪는 분들을 위해 원인과 해결 과정을 정리합니다.

목표

  • OCI ARM Ubuntu 인스턴스에 nginx 설치
  • 도메인(DuckDNS 기반)에 Let’s Encrypt 인증서 발급
  • 인증서 자동 갱신까지 설정
  • nginx 리버스 프록시 뒤에 있는 내부 서비스(모니터링 대시보드)에 Basic Auth 걸어서 노출

1. nginx 설치

OCI ARM 인스턴스도 Ubuntu 기반이면 apt 저장소에 arm64 빌드가 그대로 있어서 x86과 설치 과정이 동일합니다.

bash

sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx

설치 여부만 확인하고 싶다면:

bash

which nginx
dpkg -l | grep nginx
sudo systemctl status nginx

2. 설정 파일 소유권, 어디까지 바꿔도 될까

FTP/SFTP로 설정 파일을 편하게 올리려고 /etc/nginx 전체를 일반 계정 소유로 바꾸고 싶은 유혹이 있는데, 이건 권장하지 않습니다.

이유

  • nginx 마스터 프로세스는 root로 시작해서 80/443 같은 저번호 포트를 바인딩한 뒤, 워커 프로세스만 낮은 권한(www-data)으로 내립니다. 설정 파일 소유자가 일반 계정이면, 그 계정이 뚫리거나 실수로 잘못된 설정을 넣었을 때 root로 뜨는 nginx 마스터 프로세스가 그대로 실행해버리는 경로가 생깁니다.
  • apt upgrade로 nginx를 업데이트하면 dpkg가 소유권을 root로 강제로 되돌립니다.

절충안: 자주 건드리는 conf.d만 소유자를 바꿉니다.

bash

sudo chown -R $USER:$USER /etc/nginx/conf.d

핵심 설정(nginx.conf, mime.types)과 바이너리는 root 소유로 남기고, 실제로 서비스별 설정을 추가/수정하는 conf.d만 열어주는 방식입니다.

3. 인증서 발급 – 여기서부터 삽질 시작

nginx -t를 실행했더니 이런 에러가 떴습니다.

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem":
BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory ...)
nginx: configuration file /etc/nginx/nginx.conf test failed

설정 파일은 인증서 경로를 참조하고 있는데, 정작 인증서가 발급된 적이 없는 상태였습니다. 순서대로 원인을 하나씩 제거해나갔습니다.

3-1. certbot 미설치

bash

sudo apt install certbot python3-certbot-nginx -y

apt로 설치하면 python3-certbot-nginx 플러그인도 같이 깔려서 --nginx 방식 발급도 바로 쓸 수 있습니다. (snap 설치도 가능하지만 여기서는 apt로 진행)

3-2. webroot 방식으로 발급 시도 → 80번 포트 타임아웃

443 서버 블록이 인증서 없이는 로드가 안 되는 상태라, 처음엔 standalone 방식을 고려했지만 자동 갱신을 생각하면 webroot 방식이 정석입니다. standalone은 갱신 시마다 nginx를 멈춰야 해서 무중단 자동화가 안 되기 때문입니다.

80번 포트에 acme-challenge 경로를 여는 서버 블록을 준비했습니다.

nginx

server {
    listen 80;
    server_name example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/certbot;
        allow all;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

그런데 발급 시도하니:

Detail: x.x.x.x: Fetching http://example.com/.well-known/acme-challenge/...:
Timeout during connect (likely firewall problem)

원인 진단 순서

  1. DNS가 실제 서버 IP를 정확히 가리키는지 확인 (curl -4 ifconfig.me, nslookup) → 정상
  2. ufw status 확인 → inactive (여기가 원인이 아니었음)
  3. 서버 자체의 iptables INPUT 체인을 확인했더니, 443/22/기타 포트는 명시적 ACCEPT 규칙이 있는데 80번 포트만 규칙이 없어서 맨 마지막 REJECT 규칙에 걸리고 있었습니다.

bash

sudo iptables -L INPUT -n --line-numbers

REJECT 규칙보다 앞선 번호에 80번 ACCEPT를 삽입해서 해결:

bash

sudo iptables -I INPUT 19 -p tcp --dport 80 -j ACCEPT

OCI는 인스턴스 자체 iptables/ufw와, VCN 레벨의 Security List(클라우드 방화벽) 두 겹의 방화벽을 갖고 있습니다. 둘 다 확인 대상이지만, 이번 경우는 인스턴스 내부 iptables가 원인이었습니다.

3-3. 80번은 뚫렸는데 이번엔 404

Detail: Invalid response from http://example.com/.well-known/acme-challenge/...: 404

curl -I http://example.com으로 확인해보니 응답이 오긴 오는데, 크기가 정확히 Ubuntu nginx 기본 설치 페이지(Welcome to nginx!)와 일치했습니다. 즉 요청이 원하는 도메인의 서버 블록이 아니라 기본(default_server) 사이트로 흘러가고 있었던 것입니다.

bash

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

3-4. default 삭제 후 443 인증서 에러 재발

default_server가 없어지니, 처음에 겪었던 “인증서 파일 없음” 에러가 다시 nginx 리로드를 막았습니다. 닭이 먼저냐 달걀이 먼저냐 상황 — 인증서를 발급하려면 nginx가 떠 있어야 하는데, 인증서가 없으면 nginx 설정 자체가 안 먹힙니다.

443 서버 블록을 임시로 전체 주석 처리하고, 80번 블록(acme-challenge 포함)만 살려서 nginx를 리로드했습니다.

bash

sudo nginx -t
sudo systemctl reload nginx

이제서야 발급 성공:

bash

sudo certbot certonly --webroot -w /var/www/certbot -d example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem

인증서 발급 후 443 블록 주석을 해제하고 다시 리로드하면 됩니다.

4. 443 포트를 다른 프로세스가 이미 쓰고 있던 문제

인증서까지 발급받고 443 블록도 살렸는데, 여전히 브라우저에서 접속이 안 됐습니다 (ERR_TIMED_OUT).

bash

sudo ss -tlnp | grep :443
LISTEN 0 4096 *:443 *:* users:(("v2ray-plugin",pid=...))

nginx가 아니라 오래전에 설정해두고 잊고 있던 shadowsocks 관련 서비스가 443번을 선점하고 있었습니다.

bash

ps -ef --forest | grep -B 5 v2ray-plugin
systemctl list-units --type=service --all | grep -iE 'shadowsocks|v2ray'

부모 프로세스가 shadowsocks-server.service인 것을 확인하고, 더 이상 쓰지 않는 서비스라 중지 및 비활성화했습니다.

bash

sudo systemctl stop shadowsocks-server
sudo systemctl disable shadowsocks-server
sudo systemctl reload nginx

이제 nginx가 443을 정상적으로 점유합니다.

5. Basic Auth 403 Forbidden

443 접속은 되는데, 로그인 창에서 뭘 입력해도 403이 떴습니다. 원인은 단순했습니다 — auth_basic_user_file이 가리키는 파일 자체가 없었습니다.

bash

sudo apt install apache2-utils -y
sudo htpasswd -c /etc/nginx/.htpasswd_service_name your_username

파일을 생성하니 정상적으로 로그인 창이 뜨고 인증도 통과했습니다.

6. 인증서 자동 갱신 설정

webroot 방식으로 발급했기 때문에, 갱신 시에도 nginx를 멈출 필요가 없습니다. certbot 설치 시 systemd timer가 자동으로 등록되어 하루 두 번 만료 임박 인증서를 갱신 체크합니다.

bash

sudo systemctl list-timers | grep certbot

갱신 시 nginx가 새 인증서를 반영하도록 reload hook을 추가합니다.

bash

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null << 'EOF'
#!/bin/bash
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

갱신 테스트:

bash

sudo certbot renew --dry-run

7. iptables 규칙 영속화

iptables -I로 추가한 규칙은 재부팅하면 사라집니다. 영구 적용하려면:

bash

sudo apt install iptables-persistent -y
sudo netfilter-persistent save

마무리 – 정리하면

증상원인해결
nginx -t에서 인증서 없음 에러애초에 인증서 발급 이력 없음certbot으로 발급 필요
certbot webroot 인증 타임아웃인스턴스 내부 iptables에 80번 ACCEPT 규칙 누락iptables 규칙 추가
acme-challenge 404sites-enabled/default가 요청을 가로챔default 사이트 제거
default 제거 후 443 에러 재발인증서 없이는 443 블록 로드 불가 (선후관계 문제)443 블록 임시 주석 후 발급
443 접속 타임아웃 (인증서 발급 후)다른 프로세스(shadowsocks-server)가 443 선점미사용 서비스 중지/비활성화
로그인 시 403 Forbidden.htpasswd 파일 자체가 없음htpasswd로 파일 생성

한 줄 한 줄은 별거 아닌데, 문제들이 순차적으로 서로를 가리고 있어서 하나씩 벗겨내는 느낌으로 진행했습니다. 특히 **”이전에 설정해두고 잊어버린 다른 서비스가 포트를 선점하고 있는 경우”**는 ss -tlnp로 포트 점유 프로세스를 직접 확인하지 않으면 알아채기 어려운 유형의 문제라, nginx 관련 트러블슈팅 시 첫 단계로 포트 점유 확인을 추천합니다.