
목적
아래 환경에서 원활한 SSL 인증서 (Let's Encrypts) 처리를 위한 ACME 설정을 하고자 합니다.
- Proxmox 9 ACME
- Aws Route 53
- Nginx (Container)
- 내부망 (개발서버)
- public 도메인
일반 온프레미스 서버였다면 Nginx 서버 내에서 certbot등을 설치하여 진행했을테지만,
다행히 Proxmox 에서는 ACME 를 통해 인증서를 자동으로 갱신하고 이를 컨테이너에 쉽게 배포할 수 있도록 제공하고 있었습니다.
이를 활용한 설정 함께 진행 해 보겠습니다.
Let's Encrypts 조건
우선 Let's Encrypts ACME에서 인증서를 관리하려면 아래의 조건이 이루어져야 합니다.
- 계정을 등록하려면 Let's Encrypt의 이용 약관에 동의해야 합니다.
- 노드의 포트 80은 인터넷에서 접근 가능해야 합니다.
- 포트 80에 다른 리스너가 없어야 합니다.
- 요청된 (하위) 도메인은 노드의 공용 IP로 확인되어야 합니다.
도메인 레코드 수정
우선 도메인에 사설아이피를 등록하는 것부터 진행하겠습니다.
Route53에서 개발용으로 사용할 도메인을 추가합니다.
제 경우 마침 회사에서 사용하는 도메인이 있었고, 해당 도메인에 레코드만 수정하여 서브도메인을 사용할 예정입니다.
Aws Route53에서 레코드생성을 통해 아래와 같이 A레코드로 사내 내부망 사설 아이피를 등록해 줍니다.

레코드 이름은 사용할 이름, 값에 사설 IP를 적어줍니다.
레코드 유형은 A, TTL은 전 그냥 1일정도로 주로 하는 편입니다.

dev-api는 백엔드, dev는 프론트엔드, pve는 proxmox를 지정하였습니다.
사설 아이피를 등록했으니, 외부망에서는 도메인을 알아도 접속이 안되고 사내망에서만 접속이 되는것을 확인할 수 있습니다.
DNS서버까지 만질 생각은 없기에 각 도메인제공업체의 DNS를 통해 이제 제가 지정한 사설아이피와 도메인정보는 널리 퍼지기 시작할겁니다.
Proxmox ACME 설정
먼저 계정등록먼저 진행해줍니다.

데이터센터 - ACME를 눌러 위와 동일한 페이지에 접속 후 추가를 눌러 계정을 등록합니다.

ACME 디렉토리는 Let's Encrypt V2와 Let's Encrypt V2 Staging 으로 이루어져 있습니다.
Staging 은 검증 절차등이 간소화되었다고 들었습니다만 한번에 안될것 같지 않아 저는 그냥 시작부터 Let's Encrypt V2로 만들었는데,
Staging으로 진행하시는 분들은 이후에 완료 후 계정을 새로 만들어 바꿔주기만 하면 되니 잘못 선택하더라도 그냥 진행하시면 됩니다.

저흰 인터넷 접근이 불가능한 내부망에서만 접근 가능한 개발서버를 구축하는 중에 있어 분명 ACME Let's Encrypts 검증 과정에서 실패가 뜰 가능성이 매우 높습니다.
이로인해 AWS의 Route53을 Proxmox ACME쪽에서 제어할 수 있도록 관련 플러그인 설정을 해줍니다.

챌린지 플러그인을 추가하기 위해 추가버튼을 누루면 아래와 같은 모달창이 뜨는 것을 확인할 수 있습니다.

구분 표시 아이디는 아무거나 정하시고, Access Key와 Secret Access Key를 넣어줘야하지만, 저흰 아직 IAM을 만들지 않았습니다.
AWS IAM , 정책, 액세스키 생성
AWS를 로그인 후 IAM 사용자쪽으로 들어가 사용자 생성을 시도합니다.



사용자 세부 정보를 지정하고나서 권한설정에 들어가 직접 정책 연결을 체크합니다.
이후 하단에 생긴 정책 생성 버튼을 누룬 후 JSON 형식으로 보기를 눌러 편집모드로 들어갑니다.

Proxmox ACME에서 그렇게 중요한 권한이 필요하지 않습니다.
사실 해당 정보도 꽤나 큰 권한이지만 필요한 부분이기에 관련 부분까지만 허용할 예정입니다.
혹시 더 보안적으로 주시고 싶으시면 호스티드존(특정도메인)의 ARN을 걸어 리소스쪽의 제한을 해야 안전할 것 같습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets",
"route53:GetChange",
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": "*"
}
]
}위 코드를 복사하여 사용하세요.

정책 이름을 지정해주고, 설명을 기입 후 정책 생성 버튼을 눌러 정책을 생성합니다.

다시 IAM 생성 페이지로 넘어오게 되는데 여기서 권한 정책에 새로고침 버튼을 누룬 후 지정한 정책명을 검색하여 체크합니다.

이렇게 Proxmox 용 사용자가 생성되었습니다.
이제 엑세스키를 만들어야 합니다.
생성된 사용자를 클릭합니다.

생성된 사용자 정보에 접속 후 보안 자격 증명 탭을 눌러 액세스 키 만들기를 눌러주세요.

액세스 키 모범 사례 및 대안은 기타로 하셔도 충분합니다.

바로 액세스 키 만들기 클릭

지금 이 순간이 지나면 액세스키를 알 수 있는 방법은 없습니다.
.csv를 다운로드 받거나 잘 백업해둡니다.
Proxmox 페이지에 Access Key Id 와 Secret Access Key 를 삽입해 ACME 챌린지 플러그인 등록을 마무리합니다.
루트 노드 내 ACME 사용 도메인 등록
ACME 계정 및 챌린지 플러그인 등록이 마무리 되었습니다.
이제 루트 노드 - 인증서에 들어가 ACME 내 추가 버튼을 눌러 도메인을 등록합니다.


챌린지 유형은 DNS, 플러그인은 아까 등록한 챌린지 플러그인을 지정합니다.
제경우 aws-route-53-acme 라는 이름으로 지정했습니다.
서브도메인의 경우 A레코드로 지정한 서브도메인들을 등록해줍니다.
등록이 완료되면 지금 인증서 발급을 눌러 인증서 발급을 진행할 수 있습니다.

이후 상위목록에 pveproxy-ssl.pem 이 하나 생긴 걸 확인할 수 있습니다.
제 경우 총 3개의 도메인을 등록했습니다.

각 도메인 별로 약 1분 안으로 인증은 완료됩니다.

위 과정까지 완료라면 아래 경로에 pem 파일과 key 파일이 생성됩니다.
/etc/pve/local/pveproxy-ssl.pem
/etc/pve/local/pveproxy-ssl.key
위 생성된 키 파일은 와일드카드 인증서와는 조금 결이 다릅니다.
기존에 도메인들을 등록하고 이를 통해 생긴 인증서다 보니 SAN 방식으로 유추됩니다.
이 인증서를 각 컨테이너로 보내는 방법
root@pve:/etc/pve/local# pct list
VMID Status Lock Name
100 running nginx
200 running ***
300 running ***
400 running ****
410 running ****
500 running *****
600 running **
700 running ***각 VM ID를 확인해 본 후 아래 명령어를 통해 수동으로 처리할 수 있습니다. ( 제 경우 Nginx VMID가 100입니다.)
# 폴더 생성
pct exec 100 -- mkdir -p /etc/nginx/ssl
# 인증서 복사
pct push 100 /etc/pve/local/pveproxy-ssl.pem /etc/nginx/ssl/pveproxy-ssl.pem
pct push 100 /etc/pve/local/pveproxy-ssl.key /etc/nginx/ssl/pveproxy-ssl.key
# 파일 권한 설정
pct exec 100 -- chown root:www-data /etc/nginx/ssl/pveproxy-ssl.*
pct exec 100 -- chmod 640 /etc/nginx/ssl/pveproxy-ssl.*하지만 이걸 90일마다 처리해줘야 합니다. Let's Encrypt 정책 때문이긴 하지만, 다행히 Proxmox에서는 자체 갱신을 하고 있습니다.
Proxmox ACME 갱신 메커니즘은 다음과 같습니다.
Proxmox의 ACME 갱신 메커니즘
- 만료까지 30일 이내 남았을 때 자동으로 갱신 시도
- 매일 실행되지만 실제 갱신은 90일 주기 중 후반부 (만료 30일 전)에만 발생.
Proxmox Wiki 글 인용
ACME에서 제공하는 인증서(pvenode 또는 GUI를 통해)로 노드가 성공적으로 구성된 경우, pve-daily-update.service를 통해 인증서가 자동으로 갱신됩니다. 현재는 인증서가 이미 만료되었거나 향후 30일 이내에 만료될 예정인 경우에만 갱신이 시도됩니다.
단기 인증서를 발급하는 사용자 정의 디렉터리를 사용하는 경우, 재부팅 후 인증서 갱신을 놓치지 않으려면 pve-daily-update.timer 단위의 임의 지연을 비활성화하는 것이 좋습니다.
ACME가 갱신한다 해도 그건 노드상의 인증서까지입니다.
각 컨테이너에는 자동 배포되지 않으므로 위 코드를 통해 수동처리해야만 합니다.
이를 자동화하기 위해 LLM에 부탁해 CronJob에서 사용할 배치스크립트 생성 요청하였습니다.
파일 저장 위치 : /usr/local/bin (컨테이너가 아닌 루트노드)
#!/bin/bash
# Configuration
CONTAINER_ID=100
NODE_NAME=dev # 설정한 노드명에 따라 변경 필수
SRC_CERT="/etc/pve/nodes/$NODE_NAME/pveproxy-ssl.pem"
SRC_KEY="/etc/pve/nodes/$NODE_NAME/pveproxy-ssl.key"
DEST_PATH="/etc/nginx/ssl"
DEST_CERT="$DEST_PATH/pveproxy-ssl.pem"
DEST_KEY="$DEST_PATH/pveproxy-ssl.key"
# Check if certificate files exist
if [[ ! -f "$SRC_CERT" || ! -f "$SRC_KEY" ]]; then
echo "Error: Certificate or key file not found."
exit 1
fi
# Create destination directory in container if it doesn't exist
pct exec $CONTAINER_ID -- mkdir -p "$DEST_PATH"
# Copy certificate and key to container (overwrites existing files)
pct push $CONTAINER_ID "$SRC_CERT" "$DEST_CERT"
pct push $CONTAINER_ID "$SRC_KEY" "$DEST_KEY"
# Set permissions in container
pct exec $CONTAINER_ID -- chown root:www-data "$DEST_CERT" "$DEST_KEY"
pct exec $CONTAINER_ID -- chmod 640 "$DEST_CERT" "$DEST_KEY"
# Restart Nginx in container to apply new certificate
pct exec $CONTAINER_ID -- systemctl reload nginx
echo "Certificate and key copied (overwritten if existed) and Nginx reload successfully."
제 노드명은 dev이지만 기본설정이신 분들은 pve로 표시됩니다.
아, 실행권한이 부여되지 않았네요.
chmod +x /usr/local/bin/copy-cert-to-nginx.sh실행해 보겠습니다.

Reload 로만 해도 될 것 같아, 스크립트를 수정해 놨습니다.
컨테이너에 접속해 파일이 잘 들어왔나 확인해 봅니다.

잘 들어와 있습니다.
이후에 루트노드에서 CronTab을 설정합니다.
리눅스 크론탭이란?
쉽게 말해 시간표대로 명령을 자동 실행 시켜주는 도구입니다.
작성된 규칙대로 움직이며 자세한 부분은 따로 검색해 보시는 걸 추천합니다.
단순히 보면 [분 시 일 월 요일 명령어]로 동작됩니다.
예) 매일 자정(00:00)에 myscript.sh 실행
0 0 * * * /usr/local/bin/myscript.sh
예) 매 매시간 30분마다 myscript.sh 실행
30 * * * * /usr/local/bin/myscript.sh
예) 매 월요일 오전 9시 myscript.sh 실행
0 9 * * 1 /usr/local/bin/myscript.sh
crontab -e
# 이후 편집기 선택이 있는데 취향에 따라 선택하시면 됩니다. 1)NANO 2)VIM# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
0 0 * * * /usr/local/bin/copy-cert-to-nginx.sh
현재 CronTab이 잘 적용되었는지 아래 명령어를 통해 확인할 수 있습니다.
crontab -l # 현재 설정 확인여기에 결과 로그를 남기고 싶은 분은 리눅스의 리다이렉션(>>) 을 이용해 아래와 같이 추가하시면 됩니다.
0 0 * * * /usr/local/bin/copy-cert-to-nginx.sh >> /var/log/copy-cert-to-nginx.log 2>&1리다이렉션에 대해서는 추후에 다른 글로 자세히 설명하겠습니다.
간단히 설명하면 2>&1는 stderr를 stdout과 같은 파일로 합친다는 뜻입니다. 즉 둘 다 한 파일에 표현한단 뜻입니다.
자 이제 인증서는 다 준비되었고 nginx 컨테이너에 접속해 직접 nginx를 처리합니다.
proxmox 쪽 conf 만 공유합니다. 이를 참고하여 다른 쪽 nginx를 활용한 리버스 프록시 설정을 진행하시면 됩니다.
저장 위치 : /etc/nginx/conf.d/proxmox.conf
upstream proxmox {
server {PROXMOX 아이피};
}
server {
listen 80;
server_name {대상 도메인};
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name {대상 도메인};
# SSL
ssl_certificate /etc/nginx/ssl/pveproxy-ssl.pem;
ssl_certificate_key /etc/nginx/ssl/pveproxy-ssl.key;
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 $http_host;
proxy_connect_timeout 3600s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
send_timeout 3600s;
location / {
proxy_pass https://{PROXMOX 아이피}:8006;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off; # disable cache/buffer
proxy_ssl_verify off; # PVE
}
client_max_body_size 0;
}
nginx -t 를 통해 설정파일의 이상유무를 확인한 후 리로드 처리합니다.
nginx -t
service nginx reload
자 이제 proxmox 을 지정한 서브도메인을 통해 접속해보면 정상적으로 ssl이 적용됨을 확인할 수 있습니다.
'운영체제 및 서버 > Proxmox' 카테고리의 다른 글
| Proxmox 9 Nvidia GPU Passthrough (0) | 2025.09.07 |
|---|---|
| 사내 MFA 인증을 위한 Proxmox Android X86 VM 세팅 (0) | 2025.09.06 |
| Proxmox 9 업데이트를 위한 기본 Repositories 변경 (0) | 2025.08.29 |
| AMD APU 내장 그래픽 Proxmox GPU Passthrough 설정 ( X300 ) (4) | 2025.03.01 |