Skip to main content
SSL Without Nginx: Let's Encrypt for FastAPI
  1. Posts/

SSL Without Nginx: Let's Encrypt for FastAPI

·5 mins· ·
guide ssl https devops lets encrypt
Juseok Oh
Author
Juseok Oh
DevOps Engineer @ Hamalab
Table of Contents
ssl - This article is part of a series.
Part 1: This Article

WAS(nginx, apache)를 거치지 않는 인증 방식이 업무를 하다 보면 필요할때가 있다. SPA나 모바일 앱을 개발할 때 JWT 토큰을 쓰게 되거나, 마이크로서비스끼리 직접 통신이 필요할 때, 또는 IoT 기기처럼 가벼운 시스템을 다룰 때 말이다. 이런 상황들에서 무거운 WAS 대신 직접 인증 방식이 더 효율적인 해결책이 되기도 한다.

이 글에서는 Let’s Encrypt 인증서를 nginx나 apache 없이 발급하는 방법들을 아래 정보 기준으로 설명하겠다.

  • 도메인 test.example.com (도메인은 구매해야한다.)
  • 이메일 test@example.com

1. 인증서 발급
#

1-1. certbot standalone 방식
#

이 방법은 certbot이 자체적으로 임시 웹서버를 실행하여 인증한다.

# certbot 설치 (Ubuntu/Debian 기준)
sudo apt-get update
sudo apt-get install certbot

# standalone 모드로 인증서 발급
sudo certbot certonly --standalone -d test.example.com --email test@example.com --agree-tos --no-eff-email

1-2. DNS 인증 방식
#

DNS 공급자의 API를 통해 TXT 레코드를 자동으로 설정하는 방법이다. DNS 제공업체에 따라 플러그인이 달라진다. 예를 들어 Cloudflare를 사용한다면,

# Cloudflare 플러그인 설치
sudo apt-get install certbot python3-certbot-dns-cloudflare

# Cloudflare API 토큰 설정 파일 생성
sudo mkdir -p /etc/letsencrypt/secrets/
sudo vi /etc/letsencrypt/secrets/cloudflare.ini

# 파일에 다음 내용 추가:
# dns_cloudflare_email = test@example.com
# dns_cloudflare_api_key = YOUR_API_KEY

# 파일 권한 설정
sudo chmod 600 /etc/letsencrypt/secrets/cloudflare.ini

# 인증서 발급
sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/secrets/cloudflare.ini \
  -d test.example.com \
  --email test@example.com \
  --agree-tos --no-eff-email

1-3. 수동 DNS 인증 방식
#

DNS 플러그인 없이 수동으로 TXT 레코드를 설정하는 방법이다.

sudo certbot certonly --manual --preferred-challenges dns \
  -d test.example.com \
  --email test@example.com \
  --agree-tos --no-eff-email

이 명령어를 실행하면 화면에 추가해야 할 TXT 레코드 정보가 표시되는데 DNS 관리 패널에서 이 레코드를 추가한 후 진행하면 된다.

1-4. webroot 방식 (기존 웹서버 있을 경우)
#

이미 다른 웹서버가 실행 중이라면 webroot 방식을 사용할 수 있다.

# 웹서버 루트 디렉토리를 가정 (예: /var/www/html)
sudo certbot certonly --webroot -w /var/www/html \
  -d test.example.com \
  --email test@example.com \
  --agree-tos --no-eff-email

1-5. acme.sh 사용 (certbot 대체)
#

certbot 외에 더 가벼운 acme.sh 클라이언트를 사용하는 방법

# acme.sh 설치
curl https://get.acme.sh | sh

# DNS API 방식 (Cloudflare 예시)
export CF_Email="test@example.com"
export CF_Key="YOUR_API_KEY"
~/.acme.sh/acme.sh --issue --dns dns_cf -d test.example.com

# 또는 standalone 방식
~/.acme.sh/acme.sh --issue --standalone -d test.example.com

발급된 인증서는 각 방식에 따라 다른 위치에 저장되며, certbot의 경우 보통 /etc/letsencrypt/live/test.example.com/ 디렉토리에 저장된다. 이 인증서를 애플리케이션에서 직접 사용하면 된다.

2. 방식별 인증서 파일 위치
#

  • certbot : /etc/letsencrypt/live/test.example.com/
  • acme.sh : ~/.acme.sh/test.example.com/

3. FastAPI 애플리케이션 SSL 적용
#

필요한 파일

  • fullchain.pem (인증서 체인)
  • privkey.pem (개인 키)
import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

if __name__ == "__main__":
    uvicorn.run(
        "main:app", 
        host="0.0.0.0", 
        port=443, 
        ssl_keyfile="/etc/letsencrypt/live/test.example.com/privkey.pem",
        ssl_certfile="/etc/letsencrypt/live/test.example.com/fullchain.pem"
    )

권한 문제 해결
Let’s Encrypt 인증서는 보통 root 권한으로 관리되기 때문에 일반 사용자 권한으로 실행되는 FastAPI 앱에서 인증서를 읽을 때 권한 문제가 발생할 수 있다.
이를 해결하는 방법

# 인증서 파일 복사
sudo mkdir -p $HOME/certs
sudo cp /etc/letsencrypt/live/test.example.com/fullchain.pem $HOME/certs/
sudo cp /etc/letsencrypt/live/test.example.com/privkey.pem $HOME/certs/

# 권한 변경
sudo chown $USER:$USER $HOME/certs/fullchain.pem
sudo chown $USER:$USER $HOME/certs/privkey.pem
sudo chmod 600 $HOME/certs/privkey.pem

그리고 FastAPI 코드에서 복사한 위치를 사용한다.

uvicorn.run(
    "main:app", 
    host="0.0.0.0", 
    port=443, 
    ssl_keyfile="/path/to/your/home/certs/privkey.pem",
    ssl_certfile="/path/to/your/home/certs/fullchain.pem"
)

systemd 서비스로 실행 (권장)
애플리케이션을 systemd 서비스로 등록하면 권한 문제를 더 체계적으로 관리할 수 있다.

# /etc/systemd/system/fastapi-app.service 파일 생성
sudo vi /etc/systemd/system/fastapi-app.service

다음 내용을 입력한다.

[Unit]
Description=FastAPI Application
After=network.target

[Service]
User=user
Group=user
WorkingDirectory=/path/to/your/app
ExecStart=/path/to/your/venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 443 --ssl-keyfile /etc/letsencrypt/live/test.example.com/privkey.pem --ssl-certfile /etc/letsencrypt/live/test.example.com/fullchain.pem

[Install]
WantedBy=multi-user.target

서비스를 활성화한다.

sudo systemctl daemon-reload
sudo systemctl enable fastapi-app
sudo systemctl start fastapi-app

4. 인증서 자동 갱신
#

4-1. 애플리케이션과 상관없이
#

1. DNS 인증 방식
#

가장 간단한 방법은 HTTP 챌린지 대신 DNS 챌린지를 사용하는 것이다. DNS 챌린지는 80/443 포트에 접근할 필요가 없어 애플리케이션을 중단하지 않아도 된다.

크론탭을 열어 다음 명령을 추가한다.

sudo crontab -e

0 3 1,15 * * certbot renew --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/secrets/cloudflare.ini --quiet

certbot은 기본적으로 80/443 포트를 사용하지만, 다른 포트를 사용하도록 설정할 수도 있다.
이 방법은 기존 80/443 포트를 사용하는 FastAPI 애플리케이션에 영향을 주지 않는다.

sudo crontab -e

0 3 1,15 * * certbot renew --standalone --http-01-port 8080 --https-01-port 8443 --quiet

2. acme.sh 방식
#

# API 환경변수 설정 후 DNS 방식으로 갱신
export CF_Email="test@example.com"
export CF_Key="YOUR_API_KEY"
~/.acme.sh/acme.sh --issue --dns dns_cf -d test.example.com --renew

3. webroot 방식
#

FastAPI 애플리케이션이 특정 디렉토리에 파일을 제공할 수 있다면, webroot 방식을 사용할 수 있다.
먼저, FastAPI에 정적 파일 서빙 경로를 추가한다.

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

# 정적 파일 경로 마운트 (Let's Encrypt 인증용)
app.mount("/.well-known", StaticFiles(directory="/path/to/webroot/.well-known"), name="well-known")

인증서 갱신 설정을 하기위해 크론탭을 열어 명령어를 추가한다.

sudo crontab -e

0 3 1,15 * * certbot renew --webroot -w /path/to/webroot --quiet

인증서가 갱신된 후 필요한 복사 작업과 권한 설정을 자동화한다.

sudo crontab -e

0 3 1,15 * * certbot renew --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/secrets/cloudflare.ini --quiet && cp /etc/letsencrypt/live/test.example.com/fullchain.pem /home/user/certs/ && cp /etc/letsencrypt/live/test.example.com/privkey.pem /home/user/certs/ && chown user:user /home/user/certs/*.pem && chmod 600 /home/user/certs/privkey.pem

인증서를 발급받은 후, 다음과 같이 파일 시스템 권한을 통해 애플리케이션에 공유할 수 있다.

sudo groupadd cert-users
sudo usermod -a -G cert-users www-data  # Let's Encrypt 사용자
sudo usermod -a -G cert-users fastapi-user  # FastAPI 실행 사용자

인증서 권한을 수정한다.

sudo mkdir -p /etc/ssl/shared
sudo cp /etc/letsencrypt/live/test.example.com/fullchain.pem /etc/ssl/shared/
sudo cp /etc/letsencrypt/live/test.example.com/privkey.pem /etc/ssl/shared/
sudo chown -R root:cert-users /etc/ssl/shared
sudo chmod 750 /etc/ssl/shared
sudo chmod 640 /etc/ssl/shared/*.pem

갱신된 후 필요한 복사 작업과 권한 설정을 자동화하는 쉘 스크립트를 작성한다.

sudo vi /etc/letsencrypt/renewal-hooks/post/copy-certs.sh
#!/bin/bash
cp /etc/letsencrypt/live/test.example.com/fullchain.pem /etc/ssl/shared/
cp /etc/letsencrypt/live/test.example.com/privkey.pem /etc/ssl/shared/
chown -R root:cert-users /etc/ssl/shared
chmod 750 /etc/ssl/shared
chmod 640 /etc/ssl/shared/*.pem

작서한 쉘 스크립트에 실행 권한을 추가한다.

sudo chmod +x /etc/letsencrypt/renewal-hooks/post/copy-certs.sh

4-2. 애플리케이션과 함께
#

1. Cron을 사용한 자동 갱신
#

sudo crontab -e

0 3 1,15 * * certbot renew --standalone --pre-hook "systemctl stop fastapi-app" --post-hook "systemctl start fastapi-app" --quiet

위 명령어는,

  • 갱신 전에 FastAPI 애플리케이션을 중지(–pre-hook)
  • 인증서 갱신 실행
  • 갱신 후 FastAPI 애플리케이션 다시 시작(–post-hook)

2. systemd timer 사용
#

sudo vi /etc/systemd/system/certbot-renewal.service

[Unit]
Description=Certbot Renewal Service
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --standalone --pre-hook "systemctl stop fastapi-app" --post-hook "systemctl start fastapi-app" --quiet
sudo vi /etc/systemd/system/certbot-renewal.timer

[Unit]
Description=Timer for Certbot Renewal

[Timer]
OnCalendar=*-*-01,15 03:00:00
RandomizedDelaySec=1800
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable certbot-renewal.timer
sudo systemctl start certbot-renewal.timer

3. acme.sh를 사용할 경우
#

acme.sh는 설치 시 자동으로 cron 작업을 추가한다.

~/.acme.sh/acme.sh --install-cert -d test.example.com \
  --key-file /path/to/privkey.pem \
  --fullchain-file /path/to/fullchain.pem \
  --reloadcmd "systemctl restart fastapi-app"

이렇게 설정하면 인증서 갱신 시 자동으로 FastAPI 애플리케이션을 재시작한다.

Let’s Encrypt 인증서는 보통 90일마다 만료되므로, 60일 주기로 갱신하는 것이 안전하다. 위 방법들 중 어느 것을 사용하든 갱신 스크립트가 실패하지 않도록 주기적으로 테스트하는 것이 중요하다.

ssl - This article is part of a series.
Part 1: This Article

Related

게으른 개발자의 IDE, LazyVim
·3 mins
guide vim ide tool
Teleport, 쿠버네티스로 배포하기
·4 mins
guide teleport self-hosted k8s
Self hosted Teleport 서버 구성하기
·2 mins
guide teleport self-hosted
[AWS] EC2 에 EFS 마운트하기
·1 min
guide AWS EC2 EFS
[AWS] EC2 신규 EBS 스토리지 추가
·3 mins
guide AWS EC2 EBS out-scale
[SonarQube] 외부 플러그인 설치 방법
·1 min
guide SonarQube