오라클 클라우드 무료 서버에 내 도메인 연결하기: DNS A 레코드, Nginx 리버스 프록시, Let's Encrypt 무료 HTTPS
· 18분 읽기
도메인 A 레코드를 오라클 예약 IP에 연결하고, Nginx 리버스 프록시 설정과 Certbot으로 Let's Encrypt 무료 인증서를 받아 HTTPS와 자동 갱신까지 적용한 기록입니다.
이 글에서 다루는 내용
- 2편에서 미뤄 둔 임시 IP → 예약 IP 전환
- 가비아 네임서버를 Cloudflare로 넘기고 A 레코드 두 개(@, www) 등록하기
- ping 대신 nslookup으로 도메인과 IP 연결 확인하기
- Nginx에 도메인 전용 리버스 프록시 설정 파일 만들기
- Certbot으로 Let’s Encrypt 무료 인증서를 받아 HTTPS 적용하기
- 자동 갱신 타이머와 갱신 모의 실행(dry-run) 확인하기
- 인증서 발급 실패와 502 Bad Gateway가 뜰 때 볼 곳
2편에서 UFW 방화벽과 Swap 4GB, 시간대까지 정리하고 나니 서버는 켜 두고 신경 쓰지 않아도 되는 상태가 됐습니다. 다만 공인 IP는 임시 IP 그대로였고, 예약 IP로 바꾸는 일은 도메인을 연결할 때 한꺼번에 하기로 미뤄 두었습니다. 접속하려면 여전히 숫자 IP를 입력해야 했고, 브라우저 주소창에는 주의 요함 표시가 떴습니다. 2편 끝에서는 다음 글을 Docker로 예고했지만, 서비스를 올리기 전에 주소와 인증서부터 갖춰 두는 편이 순서상 맞다고 판단해 이 글을 먼저 썼습니다.
먼저 2편에서 미룬 예약 IP 전환을 끝내고, Cloudflare에서 도메인의 A 레코드를 그 예약 IP에 연결했습니다. 그다음 Nginx가 도메인으로 들어온 요청을 서버 안쪽 서비스 포트로 넘기도록 리버스 프록시를 설정하고, Certbot으로 Let’s Encrypt 인증서를 받아 HTTPS를 적용했습니다. 도메인 등록비 말고는 추가 비용 없이, 인증서 발급과 Nginx HTTPS 설정이 Certbot 명령 한 줄로 끝났습니다.

테스트 환경
- 캡처 기준: 2026-10-26
- 홈 리전: Japan East (Tokyo)
- OS 이미지: Canonical Ubuntu 24.04
- 셰이프: VM.Standard.A1.Flex 2 OCPU / 12GB
- 웹 서버: Nginx(1편에서 설치), Certbot(Ubuntu apt 패키지)
- 로컬: Windows 11 + cmd, Chrome
- 이전 글: 오라클 무료 서버 안정화, 임시 IP의 정체와 UFW·Swap·타임존 설정
01 2편에서 미룬 예약 IP 전환
2편에서 확인한 것처럼, 서버를 만들 때 자동으로 붙은 공인 IP는 임시 IP이고 번호 그대로 예약 IP로 바꾸는 기능은 없습니다. 2편 끝에서는 반납 직전에 멈췄는데, A 레코드에 넣을 IP가 정해져야 하므로 이번 작업의 첫 단계로 전환을 마쳤습니다. 이 IP를 아직 다른 곳에 등록해 둔 적이 없어서, 번호가 바뀌어도 잃을 것이 없다고 판단했습니다.
편집 창까지 가는 경로는 2편과 같습니다.
컴퓨트 → 인스턴스 → 인스턴스 이름 → 네트워킹 탭 → 연결된 VNIC → 기본 VNIC
→ IP 관리 탭 → IPv4 주소 줄 오른쪽 점 세 개(···) → 편집
| 순서 | 편집 창에서 고른 것 | 결과 |
|---|---|---|
| 1 | 퍼블릭 IP 유형: 퍼블릭 IP 없음 → 업데이트 | 임시 IP가 반납되고 외부 접속이 끊김 |
| 2 | 다시 편집 → 퍼블릭 IP 유형: 예약된 퍼블릭 IP → 새 예약 IP 생성 → 업데이트 | 새 번호의 예약 IP가 연결됨 |
임시 IP는 번호 그대로 예약 IP가 될 수 없어서, 이 단계에서 서버의 공인 IP 번호가 한 번 바뀝니다. 전환이 끝난 뒤 네트워킹 → IP 관리 → 예약된 퍼블릭 IP 목록에 2편에서는 비어 있던 자리에 새 IP가 한 줄 생긴 것으로 확인했습니다. 임시 IP를 반납하고 예약 IP 하나를 만드는 구조라 공인 IPv4 무료 한도 1개 안이고, 추가 비용은 없습니다.
바뀐 IP로 SSH에 다시 접속했습니다. 같은 서버라도 IP가 달라졌기 때문에 처음 접속할 때 호스트 확인 질문이 다시 나오며, yes로 넘어가면 됩니다.
ssh -i <개인키 경로> ubuntu@<새 예약 IP>
주의: 업데이트를 누르는 순간 접속이 끊깁니다
1단계에서 업데이트를 누르면 그 자리에서 공인 IP가 사라져 SSH 세션이 끊기고, 2단계를 마칠 때까지 서버에 외부에서 접속할 수 없습니다. 새 IP는 그 뒤 A 레코드와 SSH 접속에 모두 써야 하므로 바로 적어 두어야 합니다.
02 가비아 네임서버를 Cloudflare로 넘기고 A 레코드 등록
제 도메인은 가비아에서 샀지만, DNS 관리는 Cloudflare에서 하고 있습니다. 가비아에는 네임서버를 Cloudflare로 지정해 두고(위임), 실제 레코드는 Cloudflare 대시보드에서 등록하는 구조입니다. 이 작업은 두 단계로 나뉩니다.
1단계: 가비아에서 네임서버를 Cloudflare로 변경
먼저 Cloudflare에 도메인을 추가하면, 이 도메인에 배정된 네임서버 두 개를 알려 줍니다. 제 계정에는 fonzie.ns.cloudflare.com과 rachel.ns.cloudflare.com이 배정됐고, 계정마다 이름이 다르므로 각자 Cloudflare 화면에 나온 값을 그대로 써야 합니다.
그다음 가비아 도메인 관리의 전체 도메인 목록에서 도메인을 체크하고 위쪽의 네임서버 버튼을 눌렀습니다. 열린 네임서버 변경 팝업에서 가비아 네임서버가 아니라 네임서버를 선택하고, 1차와 2차 칸에 Cloudflare 네임서버를 하나씩 넣었습니다. 팝업 아래쪽에는 소유자 인증 칸이 있어서, 휴대전화나 이메일로 본인 인증을 마쳐야 변경이 적용됩니다.
| 가비아 네임서버 칸 | 입력한 값 |
|---|---|
| 1차 | fonzie.ns.cloudflare.com |
| 2차 | rachel.ns.cloudflare.com |

네임서버 변경은 반영까지 몇 시간이 걸릴 수 있습니다. Cloudflare 대시보드에서 도메인 상태가 활성으로 바뀐 것을 보고 다음 단계로 넘어갔습니다. 가비아 화면에도 DNS 관리툴(레코드 설정)이 있지만, 네임서버를 Cloudflare로 넘긴 뒤에는 가비아 DNS 관리툴에 넣은 레코드는 적용되지 않습니다. 가비아 쪽에 예전에 넣어 둔 CNAME 같은 레코드가 남아 있어도 무시되므로, 레코드는 모두 Cloudflare에서 관리해야 합니다.
2단계: Cloudflare에서 A 레코드 등록
Cloudflare 대시보드에서 도메인을 고르고 DNS → 레코드로 이동해 레코드 추가를 눌렀습니다. A 레코드는 도메인 이름 하나를 IPv4 주소 하나에 연결하는 레코드입니다. 이름이 @인 레코드는 루트 도메인(<도메인>)을, www인 레코드는 www.<도메인>을 가리킵니다. 뒤에서 인증서를 받을 때 두 이름을 함께 넣을 것이라 이 단계에서 두 개를 같이 만들었습니다.
| 항목 | 레코드 1 | 레코드 2 |
|---|---|---|
| 형식 | A | A |
| 이름 | @ (저장하면 도메인 이름으로 표시) | www |
| IPv4 주소 | 오라클 예약 퍼블릭 IP | 오라클 예약 퍼블릭 IP |
| 프록시 상태 | DNS 전용(회색 구름) | DNS 전용(회색 구름) |
| TTL | 자동 | 자동 |
IPv4 주소에는 01절에서 새로 붙인 예약 퍼블릭 IP를 넣었습니다. 임시 IP를 넣어 두면 인스턴스를 지우고 다시 만들 때 IP가 바뀌어, 도메인이 엉뚱한 주소를 가리키게 됩니다. Cloudflare는 새 레코드의 프록시 상태를 켜진 상태(주황색 구름)로 만들기 때문에, 저장하기 전에 토글을 눌러 DNS 전용으로 바꿨습니다.

주의: 인증서를 받을 때는 회색 구름으로
프록시 상태가 주황색 구름이면 Let’s Encrypt의 HTTP 챌린지 요청이 오라클 서버가 아니라 Cloudflare를 거치게 되어 인증서 발급 검증이 실패할 수 있습니다. 주황색 구름에서는 도메인을 조회해도 오라클 IP가 아니라 Cloudflare IP가 나옵니다. 처음 인증서를 받는 단계에서는 반드시 DNS 전용(회색 구름)으로 두어야 합니다.
03 nslookup으로 도메인과 IP 연결 확인
DNS 값을 바꾸면 전 세계 DNS 서버에 퍼지기까지 시간이 걸립니다. 새로 만든 레코드는 보통 몇 분 안에 반영되고, 기존 레코드를 고친 경우에는 예전 TTL만큼 더 기다려야 할 수 있습니다. 로컬 Windows의 cmd에서 두 이름을 각각 조회했습니다.
nslookup <도메인>
nslookup www.<도메인>
권한 없는 응답:
이름: <도메인>
Address: 203.0.113.10
Address에 예약 IP가 찍히면 연결된 것입니다. 두 이름 모두 같은 IP가 나오는 것으로 확인했습니다.

처음 메모에는 ping으로 확인한다고 적어 두었는데, nslookup으로 바꿨습니다. 오라클 기본 보안 목록은 ping이 쓰는 ICMP 에코 요청을 받지 않기 때문에, 도메인이 제대로 연결돼 있어도 ping은 요청 시간 초과로 끝납니다. ping 첫 줄의 대괄호 안에도 IP가 나오기는 하지만, 뒤이은 시간 초과가 연결 실패처럼 보여 헷갈리기 쉽습니다.
04 Nginx에 도메인 전용 설정 파일 만들기
리버스 프록시(Reverse Proxy)는 입구의 안내원과 비슷합니다. 누가 https://<도메인>으로 들어오면 Nginx가 먼저 받고, 서버 안에서 따로 돌고 있는 웹 애플리케이션(예: 8080 포트의 Docker 컨테이너)에 요청을 넘긴 뒤 응답을 다시 브라우저로 돌려줍니다. 바깥에는 80·443만 열어 두고 앱은 서버 안쪽 포트에 둘 수 있어서 이 구조를 택했습니다.
서버에 SSH로 접속해 도메인 전용 설정 파일을 만들었습니다.
sudo nano /etc/nginx/sites-available/<도메인>.conf
편집기에 아래 내용을 넣고 <도메인>을 실제 도메인으로 바꿨습니다.
server {
listen 80;
server_name <도메인> www.<도메인>;
location / {
# 서버 안쪽 서비스 포트로 넘긴다. 4편에서 앱을 8080에 띄울 예정
proxy_pass http://127.0.0.1:8080;
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_set_header 네 줄은 원래 요청의 도메인(Host), 접속자 IP(X-Real-IP, X-Forwarded-For), http인지 https인지(X-Forwarded-Proto)를 뒤쪽 앱에 그대로 전해 주는 설정입니다. 이 줄이 없으면 앱 로그에는 모든 접속자가 127.0.0.1로 찍힙니다. 다 넣었으면 Ctrl + O → Enter로 저장하고 Ctrl + X로 나왔습니다.

처음 적어 둔 설정은 proxy_pass http://127.0.0.1:80;이었는데, 앱이 실제로 쓸 포트인 8080으로 고쳤습니다. 4편 전까지 앱 없이 Nginx 기본 페이지만 보여 주고 싶다면 location 안을 root /var/www/html; 한 줄로 두는 방법도 있습니다.
주의: proxy_pass를 80으로 두면 자기 자신을 부릅니다
listen 80인 server 블록에서 proxy_pass를 127.0.0.1:80으로 두면, Nginx가 받은 요청을 다시 자기 자신에게 넘기는 루프가 생깁니다. 페이지가 열리지 않고 오류 로그만 쌓이므로, proxy_pass에는 항상 뒤쪽 앱의 포트를 적어야 합니다.
05 설정 활성화와 문법 검사
sites-available에 만든 파일은 sites-enabled에 링크가 있어야 Nginx가 읽습니다. 링크를 만들고, 기본으로 들어 있던 default 링크는 지웠습니다. default는 80 포트의 기본 서버로 잡혀 있어서 남겨 두면 IP로 들어온 요청이 그쪽으로 가고, 같은 포트 설정이 두 군데에 생겨 헷갈립니다. rm -f는 링크만 지우는 것이라 sites-available/default 원본은 그대로 남습니다.
# 1. 설정 파일 활성화 (심볼릭 링크)
sudo ln -s /etc/nginx/sites-available/<도메인>.conf /etc/nginx/sites-enabled/
# 2. 기본 default 링크 제거
sudo rm -f /etc/nginx/sites-enabled/default
# 3. 문법 검사
sudo nginx -t
# 4. 이상 없으면 다시 읽기
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
nginx -t에서 syntax is ok와 test is successful 두 줄이 모두 보일 때만 reload 합니다. 문법이 틀린 채로 reload 하면 새 설정이 적용되지 않으므로, 검사를 건너뛰지 않았습니다. reload 뒤에는 아무 출력 없이 프롬프트가 돌아오는 것으로 확인했습니다.

06 Certbot 설치와 인증서 발급
Let’s Encrypt는 무료로 SSL/TLS 인증서를 발급하는 인증 기관이고, Certbot은 그 인증서를 받아 오는 프로그램입니다. python3-certbot-nginx 플러그인을 같이 설치하면 Certbot이 Nginx 설정 파일을 읽고 HTTPS 설정까지 고쳐 줍니다. 발급 과정에서 Let’s Encrypt가 80 포트로 들어와 도메인 주인을 확인하므로, 1편과 2편에서 OCI 보안 목록과 UFW에 80·443을 열어 둔 상태에서 실행했습니다.
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <도메인> -d www.<도메인>
명령을 실행하면 질문 세 개가 이어서 나옵니다.
| 질문 | 입력 |
|---|---|
| Enter email address | 계정 관련 연락을 받을 이메일 |
| Terms of Service 동의 | Y |
| EFF 소식 메일 수신 | N |

원래는 이메일을 만료 알림용으로 적어 두었지만, Let’s Encrypt는 2025년 6월부터 인증서 만료 안내 메일을 보내지 않습니다. 이메일은 계정 관련 중요 공지를 받는 용도로만 남았습니다.
질문에 답하고 잠시 기다리면 발급과 Nginx 적용이 함께 끝납니다.
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/<도메인>/fullchain.pem
Key is saved at: /etc/letsencrypt/live/<도메인>/privkey.pem
This certificate expires on <만료일>.
Deploying certificate
Successfully deployed certificate for <도메인> to /etc/nginx/sites-enabled/<도메인>.conf
Successfully deployed certificate for www.<도메인> to /etc/nginx/sites-enabled/<도메인>.conf
Congratulations! You have successfully enabled HTTPS on https://<도메인> and https://www.<도메인>

Certbot이 고친 내용은 설정 파일을 다시 열어 보면 알 수 있습니다. listen 443 ssl과 인증서 경로, http로 들어온 요청을 https로 돌리는 리다이렉트가 추가돼 있고, 추가된 줄 끝에는 # managed by Certbot이 붙어 있습니다.
sudo cat /etc/nginx/sites-available/<도메인>.conf
핵심 팁: 만료 메일 대신 타이머와 dry-run
만료 안내 메일이 오지 않으니 갱신이 실패해도 메일로 알 수 없습니다. 발급 직후 다음 두 절의 타이머 확인과 dry-run을 한 번씩 해 두면 갱신 경로에 문제가 없는지 미리 알 수 있습니다.
07 자동 갱신 타이머 확인
Let’s Encrypt 인증서의 유효 기간은 90일입니다. apt로 설치한 Certbot은 systemd 타이머(certbot.timer)를 함께 등록하고, 이 타이머가 하루 두 번 인증서를 확인해 만료 30일 전부터 자동으로 갱신합니다.
sudo systemctl status certbot.timer
Active: active (waiting)
Active 줄이 active (waiting)이면 타이머가 다음 실행 시각을 기다리고 있는 상태입니다. Trigger 줄에서 다음 실행 시각도 볼 수 있고, q를 눌러 빠져나왔습니다.

08 갱신 모의 실행(dry-run)
타이머가 돌고 있어도 실제 갱신이 통과할지는 따로 확인해야 합니다. --dry-run은 Let’s Encrypt의 시험용 서버로 갱신 과정만 흉내 내고 실제 인증서는 바꾸지 않아서, 언제 돌려도 부담이 없습니다.
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/<도메인>/fullchain.pem (success)
이 문구가 보이면 90일 뒤 실제 갱신도 같은 경로(DNS, 80 포트, Nginx 설정)로 통과한다는 뜻입니다. 반대로 여기서 실패하면 지금 발급이 됐더라도 갱신 때 같은 이유로 막힙니다.

09 브라우저에서 HTTPS 확인
Chrome 주소창에 https://<도메인>을 입력해 들어갔습니다. Chrome은 2023년 하반기부터 주소창의 자물쇠 아이콘을 사이트 정보 보기 아이콘(조절 막대 모양)으로 바꿨기 때문에, 예전처럼 초록색 자물쇠가 보이지는 않습니다. 이 아이콘을 누르면 나오는 팝업에 연결이 안전함이 표시되는 것으로 확인했습니다. http://<도메인>으로 입력해도 https 주소로 바뀌는 것까지 봤습니다.
팝업에서 연결이 안전함 → 인증서가 유효함을 누르면 인증서 뷰어가 열립니다. 발급 기관에 Let’s Encrypt(일반 이름 YE2)가, 유효 기간에 발급일부터 90일 뒤까지가 적혀 있는 것으로 확인했습니다. 06절에서 Certbot이 받은 인증서가 그대로 브라우저까지 전달되고 있다는 뜻입니다.

처음에 Cloudflare 프록시(주황색 구름)가 켜져 있는 다른 도메인으로 같은 화면을 열었을 때는 발급 기관이 Google Trust Services(WE1)로 나왔습니다. 프록시가 켜져 있으면 브라우저가 Cloudflare에 먼저 접속하기 때문에, 오라클 서버의 Let’s Encrypt 인증서가 아니라 Cloudflare가 대신 받아 둔 인증서가 보입니다. 인증서 뷰어의 발급 기관을 보면 브라우저가 오라클 서버에 직접 붙었는지, Cloudflare를 거쳤는지 바로 알 수 있습니다.
4편 전이라 8080에 아직 앱이 없으면 페이지 내용은 502 Bad Gateway로 나옵니다. 인증서와 연결이 안전함 표시는 그와 상관없이 정상입니다.
핵심 팁: 앱 없이 화면만 확인하려면
서버에서
python3 -m http.server 8080 --bind 127.0.0.1을 잠깐 실행하면 8080에 파일 목록 페이지가 뜨고, 브라우저에서 502 대신 그 페이지가 보입니다. 확인이 끝나면Ctrl + C로 끕니다.
10 발급이 실패하거나 502가 뜰 때
Certbot이 도메인 확인에 실패하면 아래 문구가 나옵니다. 예전 버전의 Certbot에서는 Failed authorization procedure로 표시됩니다.
Certbot failed to authenticate some domains (authenticator: nginx). The Certificate Authority reported these problems:
원인은 대부분 두 가지입니다. 첫째, 80 포트가 막혀 있는 경우입니다. OCI 콘솔의 보안 목록 수신 규칙과 서버의 sudo ufw status 양쪽에서 80·443이 열려 있어야 합니다. 둘째, A 레코드가 아직 퍼지지 않은 경우입니다. 03절의 nslookup을 다시 해 보고 예약 IP가 나올 때 5분쯤 뒤에 다시 시도하면 됩니다. Cloudflare를 쓴다면 주황색 구름이 켜져 있지 않은지도 같이 봅니다.
주의: 실패를 반복하면 잠깁니다
Let’s Encrypt는 같은 도메인의 확인 실패가 한 시간에 5번 쌓이면 한동안 발급을 막습니다. 원인을 고친 뒤 바로 본 발급을 다시 하기보다
sudo certbot certonly --nginx --dry-run -d <도메인> -d www.<도메인>으로 시험용 서버에서 먼저 통과하는지 보는 편이 안전합니다.
접속했을 때 502 Bad Gateway가 뜨는 것은 인증서 문제가 아닙니다. Nginx는 정상인데 proxy_pass가 가리키는 8080에 응답할 서비스가 없다는 뜻입니다. 서버에서 아래 명령으로 확인할 수 있습니다.
curl -I http://127.0.0.1:8080
Connection refused가 나오면 8080에서 아무것도 돌고 있지 않은 것입니다. 4편에서 Docker로 앱을 8080에 띄우면 이 화면은 사라집니다.
정리
- A 레코드는 @와 www 두 개를 예약 IP로 등록했고, 인증서도 두 이름을 함께 받았습니다.
- 연결 확인은 ping이 아니라 nslookup으로 합니다. 오라클 기본 보안 목록이 ICMP 에코 요청을 막아 ping은 시간 초과로 끝납니다.
- 처음 적어 둔 proxy_pass 127.0.0.1:80은 Nginx가 자기 자신을 부르는 설정이라 앱 포트인 8080으로 고쳤습니다.
- certbot –nginx 한 줄로 인증서 발급과 Nginx HTTPS 설정, http→https 리다이렉트가 같이 들어갑니다. 80 포트가 열려 있어야 발급됩니다.
- 만료 안내 메일이 더는 오지 않으므로 certbot.timer와 renew –dry-run으로 갱신 경로를 확인해 두었습니다.
마치며
실제 90일 뒤 자동 갱신은 아직 겪어 보지 않았습니다. 첫 갱신 시점이 오면 /var/log/letsencrypt/ 로그와 인증서 만료일이 바뀌었는지 확인해 이 글에 덧붙일 예정입니다. 지금은 8080에 앱이 없어 페이지가 502로 나옵니다.
다음 글은 Docker 설치와 첫 웹 서비스 배포입니다. 8080에 컨테이너를 띄워 이번에 만든 리버스 프록시 뒤에 붙이면, 도메인과 HTTPS를 거쳐 앱까지 이어지는 것을 확인할 수 있습니다.
참고한 곳
- #오라클클라우드
- #OCI
- #무료서버
- #Nginx
- #HTTPS
연재 글 목록 · 오라클 클라우드 무료 서버 만들기
- 1편오라클 클라우드 무료 서버 구축 가이드: 가입부터 PAYG 전환, Ampere 인스턴스 생성까지
- 2편오라클 무료 서버 안정화, 임시 IP의 정체와 UFW·Swap·타임존 설정
- 3편오라클 클라우드 무료 서버에 내 도메인 연결하기: DNS A 레코드, Nginx 리버스 프록시, Let's Encrypt 무료 HTTPS (지금 읽는 글)
- 4편오라클 클라우드 무료 서버에 Docker 올리기: Portainer 관리, Uptime Kuma 배포와 서브도메인 HTTPS 연결