AWS Lightsail에 Outline Server 구축하기

15분 읽기

목차

중국에선 VPN이 없으면 살아가기 힘들다. 자국 서비스를 보호한다는 명목으로 인스타그램, 페이스북 같은 해외 서비스를 모두 막아놨기 때문이다. 근데 그렇다고 상용 서비스를 사용하자니 불편한 점이 많다. 서버가 고장나는 경우도 많고, 그렇다고 복구가 빨리 이뤄지는 것도 아니고, 가격이 저렴하지도 않다.

대부분의 상용 VPN은 트래픽을 제한하기 위해 디바이스 갯수 제한을 걸어두는데, 이것도 꽤 짜치고… 품질이 좋지도 않아서 난 중국에 오자마자 내가 쓸 서버를 만들어서 VPN으로 사용했다. 고장나면 내가 고치면 되니 복구 시점도 명확하고, 비용도 생각보다 많이 들지 않고?

환경과 목표

내가 평소 사용하는 조합은 AWS Lightsail + CentOS Stream 9 + x86_64다. 명령은 서버의 Bash에서 실행하며, sudo 권한이 있는 계정으로 접속한다.

2026년 10월 5일 확인한 Outline 공식 설치 문서에는 지원 OS로 Ubuntu 20.04 LTS와 Debian 10이 적혀 있다. CentOS Stream 9는 그 목록에 없다. 근데 난 그냥 예전부터 리눅스는 CentOS를 썼다보니 이게 편해서 이렇게 쓴다. 이 조합이 공식 지원된다는 뜻은 아니다. 설치 스크립트 역시 x86_64를 요구한다.1

먼저 AWS 계정에서 Lightsail 콘솔을 열고, 내 컴퓨터에는 Outline Manager를 준비한다. Manager는 서버를 관리하는 프로그램이다. 나중에 기기에서 연결할 Outline Client와는 역할이 다르다.

완료 기준은 Lightsail 인스턴스에 고정 IPv4가 연결되고, 설치 출력에 나온 포트를 허용한 뒤, Outline Manager에서 서버를 관리할 수 있는 상태다. 인스턴스와 공인 IPv4는 비용이 발생하므로 생성 화면의 요금을 확인하고 진행한다.

1. 서버를 만들고 주소를 고정하기

Lightsail 콘솔에서 인스턴스 → 인스턴스 생성을 누른다. 인스턴스 위치를 고른 다음 Linux 운영 체제 → CentOS(CentOS Stream 9)를 선택한다. 일반적으로 서울을 사용하는데, 일본이나 홍콩을 선택해도 상관없다. 우리의 목적은 중국에서 해외 서비스를 이용하는 거니까. 만약 원하는 특정 국가가 있다면 아마존에서 지원하는 다른 국가를 선택하면 된다. 간단하다.

인스턴스 생성 화면에서 서울 리전과 Linux 운영 체제, CentOS 블루프린트를 선택한 상태
인스턴스 생성 화면에서 서울 리전과 Linux 운영 체제, CentOS 블루프린트를 선택한 상태
  • 인스턴스 위치:
    • 리전 선택: 서울
    • 가용 영역: 아무거나
  • 인스턴스 이미지 선택:
    • 플랫폼: Linux 운영 체제
    • 블루프린트 선택: CentOS
    • CentOS 세부 정보: 무시
범용 플랜, 듀얼 스택 네트워크, 월별 $5 크기를 선택한 상태
범용 플랜, 듀얼 스택 네트워크, 월별 $5 크기를 선택한 상태
  • 플랜 유형: 범용
  • 네트워크 유형 선택: 듀얼 스택
  • 크기 선택: 월별 $5

크기는 원하는 대로 선택해도 되는데, 사실 $5 정도만 돼도 사용하는 데는 큰 문제가 없다. 만약 부족하게 느껴진다면 $7를 선택해도 괜찮다.

인스턴스 이름 입력 칸
인스턴스 이름 입력 칸

인스턴스 이름은 기본값으로 두거나 알아서 변경하고 인스턴스 생성을 누른 뒤 상태가 Running이 될 때까지 기다린다.

생성된 인스턴스 카드가 시작 중으로 표시된 상태
생성된 인스턴스 카드가 시작 중으로 표시된 상태

서버는 생겼다. 그런데 주소는 아직 고정돼 있지 않다. 기본 공인 IPv4는 인스턴스를 중지했다 다시 시작하면 바뀔 수 있다. 설치 뒤 주소가 달라지는 일을 피해야 하니, Outline보다 먼저 Static IP를 붙이도록 하자.2

인스턴스 네트워킹 탭의 고정 IP 연결 버튼
인스턴스 네트워킹 탭의 고정 IP 연결 버튼

콘솔의 네트워킹 → 고정 IP 연결을 누르고 바로 생성 및 연결을 눌러서 고정 IP를 만들자.

2. SSH로 들어가기

주소까지 정했으니 이제 서버 안으로 들어가보자. 인스턴스를 선택하고 연결 → SSH를 사용하여 연결을 누른다. 브라우저 터미널이 열리면 아래 명령을 실행한다.

연결 탭의 SSH를 사용하여 연결 버튼
연결 탭의 SSH를 사용하여 연결 버튼

먼저 시간대를 한국 시간으로 맞춘다. Outline 설치에 필요한 설정은 아니다. 나중에 로그의 시간을 읽기 편하게 하려는 선택이다.

sudo timedatectl set-timezone Asia/Seoul
timedatectl

Time zone 항목이 Asia/Seoul이면 됐다. 이어 설치 스크립트를 내려받고 실행하는 데 쓸 패키지를 준비한다.3

sudo dnf install -y wget curl openssl

3. Outline Server를 설치하기

이제 여기서 아까 설치해 둔 Outline Manager를 켜면 된다.

Outline Manager에서 Amazon Lightsail 설정을 선택하는 화면
Outline Manager에서 Amazon Lightsail 설정을 선택하는 화면

바로 아마존 라이트세일이 나온다. 바로 클릭해서 설정을 누르면 아래와 같은 설치 스크립트가 나올 거다.

Outline Manager의 Lightsail 안내에 표시된 설치 명령
Outline Manager의 Lightsail 안내에 표시된 설치 명령

공식 저장소의 OutlineFoundation/outline-apps의 설치 스크립트에서도 확인해 볼 수 있어서 굳이 여기까지 안 들어오고 미리 설치해도 되긴 하는데, 그냥 여기서 복사해서 넣는 게 훨씬 더 간편할 거다.4

sudo bash -c "$(wget -qO- https://raw.githubusercontent.com/OutlineFoundation/outline-apps/master/server_manager/install_scripts/install_server.sh)"

Docker를 일부러 설치 안 해 놨기 때문에 스크립트가 Docker 설치 여부를 물을 거다. 바로 Y로 답해서 진행하자. 강단 있게 N을 입력하면 VPN을 설치할 수가 없다.

설치가 끝나면 서버 관리용 JSON과 방화벽에 허용할 포트가 출력된다. 다음은 형태만 보여 주는 예시다. 아래 자리표시자를 Manager에 붙여 넣는 것이 아니라, 내 서버에서 나온 JSON 전체를 사용한다.

설치 완료 출력의 관리 JSON(가림)과 관리·접속 포트
설치 완료 출력의 관리 JSON(가림)과 관리·접속 포트
{"apiUrl":"<설치 출력의 관리 API URL>","certSha256":"<설치 출력의 인증서 지문>"}

이 JSON은 별도의 안전한 비공개 장소에 보관하자. apiUrl과 certSha256은 관리 접속 정보니까, 어디가서 공개하지 않도록 하자. 특히 apiUrl에는 관리 API에 접근하는 비밀 경로가 포함된다.5

같은 출력에서 아래 두 줄도 확인한다. 숫자는 설치마다 달라질 수 있으므로 내 출력에 나온 값을 기록한다.

- Management port <내 출력의 관리 포트>, for TCP
- Access key port <내 출력의 접속 포트>, for TCP and UDP

이제 서버 안에서는 Outline이 실행될 준비가 됐다. 그러나 밖에서 들어오는 연결은 아직 Lightsail 방화벽을 통과해야 한다. 방금 기록한 포트가 다음 단계에 필요한 이유다.

4. Lightsail 방화벽에서 해당 포트를 허용하기

인스턴스의 네트워킹 → 방화벽 규칙 → 규칙 추가를 누른다. 애플리케이션: 사용자 지정을 선택하고 설치 출력의 포트로 규칙을 만든다.6

용도 Protocol Port
서버 관리 TCP 출력의 Management port
기기 접속 TCP 출력의 Access key port
기기 접속 UDP 출력의 Access key port

접속 포트에는 TCP와 UDP 규칙을 각각 추가한다. 규칙 하나마다 규칙 추가를 누르고, 세 규칙의 프로토콜과 포트가 출력 내용과 일치하는지 확인한다. HTTP나, SSH에 쓰던 기존 규칙은 유지한다. Outline을 위해 모든 TCP·UDP 포트를 열 필요는 없다. 설치 출력에 나온 포트만 정확히 허용하면 된다.

방화벽 규칙 추가 창에서 사용자 지정 TCP 포트를 입력한 상태
방화벽 규칙 추가 창에서 사용자 지정 TCP 포트를 입력한 상태

이런 식으로 규칙을 추가하면 된다.

방화벽 규칙 목록에 관리 포트 TCP 규칙이 추가된 상태
방화벽 규칙 목록에 관리 포트 TCP 규칙이 추가된 상태

그럼 나중에는 저런식으로 2개가 더 추가되고, 총 규칙은 5개가 되는 게 정상이다.

5. Outline Manager에 서버를 등록하기

외부에서 들어올 길도 열었으니, 이제 내 컴퓨터에서 관리할 수 있게 연결한다. 설치 결과를 붙여 넣는 칸에 앞서 보관한 실제 JSON 전체를 넣고 화면의 완료 버튼을 누르자.

Outline Manager의 설치 출력 입력 칸에 관리 JSON(가림)을 붙여 넣은 상태
Outline Manager의 설치 출력 입력 칸에 관리 JSON(가림)을 붙여 넣은 상태

등록 뒤 서버 화면이 열리고 서버 정보와 Access Key 목록을 조회할 수 있으면 관리 연결은 확인된 것이다. 서버의 주소가 Lightsail의 고정 IPv4와 일치하는지도 확인한다.

서버 쪽 상태까지 살펴보려면 SSH 터미널에서 실행 중인 컨테이너를 조회한다. 근데 굳이 여기까지 할 필요는 없다. 교과서적인 순서 같은 거지.

sudo docker ps --format 'table {{.Names}}\t{{.Status}}'

현재 공식 스크립트의 기본 이름은 Outline 서버가 shadowbox, 자동 업데이트 컨테이너가 watchtower다. 두 이름과 실행 상태를 확인한다. 컨테이너가 실행 중이라는 사실만으로 기기의 인터넷 연결까지 검증되는 것은 아니다. 이 편에서 확인할 것은 서버 설치와 Manager의 관리 접속이다.

연결되지 않을 때

Manager에 등록되지 않는다면, 먼저 JSON을 중괄호까지 온전히 복사했는지 확인한다. 이어 apiUrl의 주소가 고정 IPv4인지, Lightsail 방화벽에 관리 포트의 TCP 규칙이 있는지, 소스 IP를 제한했다면 현재 Manager의 공인 IP가 포함되는지 확인한다. 주소나 규칙이 어긋났다면 그 항목을 바로잡고 다시 등록한다.

주소와 Lightsail 규칙이 맞는데도 연결되지 않으면 서버 내부 방화벽을 확인한다. CentOS의 firewalld는 Lightsail 방화벽과 별개다. 모든 환경에서 실행 중이라고 가정하지 않고, 먼저 상태부터 본다.

sudo systemctl is-active firewalld

active라면 실제 인터페이스가 사용하는 zone과 허용 포트를 확인한다.

sudo firewall-cmd --get-active-zones

아래 명령의 <실제-zone>을 위 출력에서 네트워크 인터페이스가 속한 zone 이름으로 바꿔 실행한다.

sudo firewall-cmd --zone=<실제-zone> --list-all

ports·services·rich rules에서 해당 포트를 허용하는 규칙이 없고 내부 방화벽이 막는 것으로 확인됐다면, 해당 zone에만 추가한다. 아래의 세 자리표시자는 내 실제 zone과 설치 출력의 숫자로 바꾼다. 방화벽을 통째로 끄지 않는다. reload 전에 SSH 허용 규칙이 영구 설정에도 있는지 확인한다. reload는 영구 설정을 다시 읽으므로 저장하지 않은 임시 규칙은 사라진다.7

sudo firewall-cmd --permanent --zone=<실제-zone> --add-port=<관리-포트>/tcp
sudo firewall-cmd --permanent --zone=<실제-zone> --add-port=<접속-포트>/tcp
sudo firewall-cmd --permanent --zone=<실제-zone> --add-port=<접속-포트>/udp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=<실제-zone> --list-ports

허용된 포트를 다시 확인한 뒤 Manager에서 재시도한다. firewalld가 비활성 상태이거나 서비스가 없다면 이 설정을 추가하지 않고 다른 원인을 살핀다.

설치가 중간에 실패했거나 컨테이너가 보이지 않는다면, 먼저 설치 출력의 마지막 오류와 안내된 로그를 확인한다. Docker가 설치된 상태라면 아래 명령으로 중지된 컨테이너도 조회할 수 있다.

sudo docker ps -a --format 'table {{.Names}}\t{{.Status}}'
sudo systemctl status docker --no-pager

Docker 설치·실행 단계가 실패했는지, 컨테이너가 생성된 뒤 종료됐는지를 구분하고 해당 오류를 해결한 뒤 다시 확인한다. CentOS Stream 9는 앞서 밝힌 공식 지원 목록 밖의 환경이므로, 패키지 설치 오류를 무시하고 다음 단계로 넘어가지 않는다. 스크립트를 무작정 다시 실행하면 기존 컨테이너 교체를 물을 수 있으니, 이미 쓰던 서버라면 상태와 관리 정보를 먼저 보존한다.

사실 이제 다 끝났다. 다음 편에서는 Access Key를 준비하고 Outline Client에 넣어, 이 서버를 실제로 써 보자.

Footnotes

  1. 확인한 공식 문서의 지원 목록과 현재 OS의 보안 지원 여부는 서로 다른 문제다. 여기 적힌 Ubuntu·Debian 버전은 공식 페이지에 표시된 내용을 옮긴 것이며, 신규 구축용 OS 추천 목록은 아니다. 현재 설치 스크립트는 uname -m이 x86_64가 아니면 중단한다. ↩

  2. AWS Static IP 안내는 인스턴스 중지·재시작 때 기본 공인 IPv4가 바뀐다고 설명한다. 고정 IP는 같은 리전의 인스턴스에 연결한다. 이 절차는 dual-stack 인스턴스를 전제로 한다. ↩

  3. wget은 스크립트를 내려받고, curl은 설치 스크립트의 HTTP 요청에, openssl은 인증서 생성과 지문 계산에 쓰인다. dnf install -y의 -y는 패키지 설치 확인에 자동으로 동의하는 옵션이다. ↩

  4. wget -qO-는 내려받은 내용을 표준 출력으로 보내고, bash -c는 그 내용을 실행한다. 현재 스크립트는 Docker가 없으면 설치를 제안하고 서버·업데이트 컨테이너를 구성한다. 검토한 스크립트의 마지막 변경 커밋은 01403ec이다. 본문의 master URL은 이후 바뀔 수 있다. ↩

  5. 관리 JSON과 기기 접속용 Access Key는 용도가 다르다. 관리 JSON을 Client에 넣지 않는다. certSha256 자체는 인증서 지문이지만, 이 글에서는 관리 JSON 전체와 함께 비공개 정보로 취급한다. ↩

  6. AWS 방화벽 규칙 안내에서 Custom의 TCP·UDP 선택 및 출발 IP 제한을 확인할 수 있다. 제한을 선택하지 않으면 해당 규칙은 모든 출발 IP를 허용한다. ↩

  7. firewalld 공식 명령 문서에서 zone 조회와 runtime/permanent 구분을 확인할 수 있다. 영구 규칙은 sudo firewall-cmd --permanent --zone=<실제-zone> --list-all로 조회하며, <실제-zone>은 현재 인터페이스의 zone 이름으로 바꾼다. ↩

이미지 크게 보기

원본 열기

목록으로 돌아가기