saip.learning notes

도메인과 HTTPS: saip.nexus 연결하기

배포 · 마지막 수정 2026-10-24

IP 주소로만 열리던 블로그에 도메인을 붙이고 HTTPS로 운영하기까지의 기록이다. 인증서는 Caddy가 자동으로 받고 갱신한다. Caddy와 인증서 발급 원리는 "Docker로 포장하기" 노트에 있다.

도메인과 DNS

sequenceDiagram
  participant B as 브라우저
  participant D as DNS
  participant S as 서버
  B->>D: saip.nexus 의 IP는?
  D-->>B: [고정 IP] (A 레코드)
  B->>S: https://saip.nexus 요청
  • 도메인: IP 주소에 붙이는 이름. 서버를 옮겨도 DNS만 바꾸면 주소가 유지된다.
  • A 레코드: "이 이름은 이 IPv4 주소"라는 한 줄. @는 도메인 자체를 뜻한다.
  • TTL: DNS 응답을 얼마나 기억할지. 처음 설정은 몇 분 만에 퍼졌다.

도메인 고르기

비어 있는지 먼저 조회: RDAP

WHOIS의 후속인 RDAP로 등록 여부를 조회할 수 있다. 응답이 404면 등록 기록이 없다(프리미엄이나 예약어일 수 있으니 최종 확인은 구매 화면에서).

curl -s -o /dev/null -w '%{http_code}\n' -L https://rdap.org/domain/saip.blog   # 404 = 미등록
# 새 gTLD는 레지스트리 RDAP 서버를 IANA 목록에서 찾아 직접 묻는다
curl -s https://data.iana.org/rdap/dns.json

너무 자주 물으면 429(요청 과다)로 막힌다.

무엇을 보고 고르나

기준 내용
갱신 가격 새 도메인 종류(gTLD)는 첫해만 싸고 2년차부터 비싼 경우가 많다. 블로그 주소는 몇 년씩 쓰므로 갱신가가 실제 비용이다
평판 한때 스팸에 많이 쓰인 도메인 종류는 회사 방화벽이나 메신저가 의심할 수 있다. 회사에서 열어 볼 학습 노트라면 중요하다
확장성 개인 이름 도메인 하나면 blog., todo. 같은 서브도메인을 무료로 늘릴 수 있다
등록처 Cloudflare Registrar는 원가에 팔고 갱신가가 같으며 DNS가 무료. 가비아는 한국어·국내 결제. Route 53은 DNS 월 요금이 따로 붙는다

saip.dev는 이미 등록되어 있어서 saip.nexus를 Cloudflare에서 샀다.

Cloudflare DNS 설정

항목 값
Type A
Name @
IPv4 [고정 IP]
Proxy status DNS only (회색 구름)
TTL Auto

Warning

주황 구름(Proxied)을 켜면 Cloudflare가 중간에서 HTTPS를 처리한다. 서버의 Caddy가 직접 인증서를 받는 구성과 충돌하므로 DNS only로 둔다.

반영 확인은 여러 DNS에 물어본다.

curl -s "https://cloudflare-dns.com/dns-query?name=saip.nexus&type=A" -H 'accept: application/dns-json'
curl -s "https://dns.google/resolve?name=saip.nexus&type=A"

Note

Windows nslookup은 결과를 한글로 출력해서 grep "Address" 같은 필터가 아무것도 못 찾았다. DNS가 안 된 줄 알았는데 필터 문제였다. DNS over HTTPS(JSON)로 물으면 언어와 상관없이 확인할 수 있다.

HTTPS 켜기: 한 줄 바꾸기

서버 .env의 DOMAIN=:80(IP로 http만)을 DOMAIN=saip.nexus로 바꾸고 다시 띄운다.

sed -i "s/^DOMAIN=.*/DOMAIN=saip.nexus/" ~/saip/.env
docker compose up -d
docker compose logs caddy | grep -o '"msg":"[^"]*certificate[^"]*"'
# "obtaining certificate" → "trying to solve challenge" → "certificate obtained successfully"

확인 결과:

확인 결과
http://saip.nexus 308 → https://saip.nexus/
https://saip.nexus 200
인증서 Let's Encrypt, 90일. Caddy가 만료 전에 자동 갱신
Google 로그인 redirect_uri https://saip.nexus/login/oauth2/code/google
echo | openssl s_client -connect saip.nexus:443 -servername saip.nexus 2>/dev/null \
  | openssl x509 -noout -issuer -subject -enddate

Google 로그인 운영 설정

  1. OAuth 클라이언트의 승인된 리디렉션 URI에 https://saip.nexus/login/oauth2/code/google 추가. 로컬용 http://localhost:8080/...은 남겨 둔다.
  2. 동의 화면을 테스트 → 프로덕션으로 게시. 테스트 상태에서는 테스트 사용자로 등록한 계정만 로그인된다. 댓글 다는 방문자도 로그인하려면 게시해야 한다. 이메일·이름·프로필만 쓰므로 심사는 없다.

앱 코드는 바꾸지 않았다. 앱이 Caddy가 붙여 주는 X-Forwarded-Proto: https를 읽어 자기 주소를 https://saip.nexus로 알기 때문이다(server.forward-headers-strategy: framework).

발견한 것: 세션 쿠키에 Secure가 없었다

set-cookie: JSESSIONID=...; Path=/; HttpOnly            ← 고치기 전
set-cookie: JSESSIONID=...; Path=/; Secure; HttpOnly    ← 고친 뒤

HTTPS 여부는 Spring이 전달 헤더로 알아채지만, 세션 쿠키는 Tomcat이 만든다. Tomcat은 Caddy에게서 http로 받으니 Secure를 붙이지 않았다. Secure가 없으면 누군가 처음 http://로 접속하는 순간 브라우저가 쿠키를 평문으로 보낼 수 있다. 운영 compose에만 설정을 넣었다(로컬 http://localhost 개발에는 영향 없음).

environment:
  SERVER_SERVLET_SESSION_COOKIE_SECURE: "true"

Tip

배포하면 응답 헤더를 직접 보자. curl -sI https://도메인/...으로 Set-Cookie, Strict-Transport-Security, Content-Security-Policy를 확인하면 프록시 뒤에서만 생기는 문제를 잡을 수 있다.

HSTS(Strict-Transport-Security: max-age=31536000 ; includeSubDomains)는 Spring Security가 HTTPS 요청에 자동으로 붙인다. 브라우저가 1년 동안 이 도메인을 https로만 접속한다.

/blog를 운영 서버로

PC의 사용자 환경변수 두 개만 바꾼다. 토큰은 환경변수끼리 복사하고 출력하지 않는다.

변수 값
BLOG_URL https://saip.nexus
BLOG_TOKEN 서버 .env의 BLOG_API_TOKEN과 같은 값(로컬 개발용과 다름)

로컬 개발 서버에 올려 보고 싶을 때는 그 창에서만 바꾼다.

$env:BLOG_URL = "http://localhost:8080"
$env:BLOG_TOKEN = [Environment]::GetEnvironmentVariable("BLOG_API_TOKEN", "User")

blog.py는 토큰이 평문으로 나가지 않게 localhost가 아니면 https만 허용한다. 그래서 IP(http)만 있던 동안에는 운영 서버로 노트를 올리지 못했고, 이 노트가 운영 서버에 바로 올라간 첫 노트다.

댓글 0

로그인하고 댓글 달기