
구글 출신 개발자들이 설립한 Tailscale은 WireGuard 프로토콜을 기반으로 한 VPN 서비스이다. 사용자의 기기 간에 피어 투 피어(P2P) 또는 릴레이 중계 방식으로 VPN 터널을 구성하여 안전한 통신을 제공한다.
NAT 우회(NAT Traversal) 기술인 STUN을 이용해 먼저 직접 연결
필요 시 UPnP IGD, NAT-PMP, PCP 등의 프로토콜을 통해 자동으로 포트를 포워딩
만약 피어 간 직접 연결이 불가능할 경우, Tailscale은 자체 DERP(Designated Encrypted Relay for Packets) 서버를 이용해 암호화된 릴레이 통신
Tailscale은 각 클라이언트에게 Carrier-Grade NAT(CGNAT)용으로 예약된 사설 IPv4 주소를 할당한다. 이는 기존 로컬 네트워크와의 IP 충돌을 피하기 위함이다.
또한 Linux 클라이언트는 SNAT(Source NAT)를 비활성화하고, 원본 IP를 그대로 유지한 채로 트래픽을 라우팅할 수 있다. 이를 통해 해당 리눅스 장비가 내부 네트워크 게이트웨이처럼 작동하며, 로컬 네트워크 뒤의 다른 장치로도 VPN을 통해 접속할 수 있다.
Tailscale 웹사이트에서 회원가입을 완료하면, 마지막 단계에서 다음과 같은 화면이 나타난다. 이 화면에는 curl 명령어가 제공되는데, 이 명령어를 서버에서 실행하면 Tailscale을 간편하게 설치할 수 있다.

설치가 완료되면 sudo tailscale up 명령어를 입력해 Tailscale 서비스를 시작한다. 명령어를 실행하면 브라우저 로그인용 URL이 출력되며, 해당 URL에 접속해 로그인하면 서버가 Tailscale 네트워크에 연결된다. 이 메시지의 마지막 부분에 임시 로그인 URL이 표시되며, 이를 통해 인증 과정을 진행할 수 있다.
$ curl -fsSL https://tailscale.com/install.sh | sh
# Installing Tailscale for ubuntu jammy, using method apt
# + sudo mkdir -p --mode=0755 /usr/share/keyrings
# [sudo] password for ----:
$ sudo tailscale up
# [sudo] password for ----:
# To authnenticate, visit:
# https://login.tailscale.com/a/?????????????????
#
해당 URL을 브라우저에서 열면 Tailscale 로그인 페이지가 나타나고, 로그인 후 Connect 버튼이 보인다. Connect를 클릭하면, 해당 서버가 Tailscale 네트워크에 등록되며 연결된 디바이스 목록에 나타나게 된다.


Windows 버전의 Tailscale을 설치한 후, 가입했던 이메일 주소로 로그인하면 자동으로 서버와 연결된다. 별도의 설정 없이도 네트워크에 참여하게 되며, 로그인과 동시에 장치가 Tailscale 네트워크에 등록된다.
Tailscale 웹 대시보드에 접속하면, 현재 연결된 기기 목록을 확인할 수 있다. 각 기기별로 할당된 IP 주소, 기기 이름, 최근 접속 시각 등의 정보도 함께 제공된다.



Tailscale 대시보드에서 My Devices를 클릭하면, 연결된 서버와 클라이언트 목록을 확인할 수 있다. 각 장치 옆에는 100.xxx.xxx.xxx 형식의 IP 주소가 표시되는데, 이는 Tailscale이 자동으로 할당한 가상 네트워크 주소다.
이 주소는 실제 사설망처럼 작동하며, SSH 접속, 데이터베이스 연결, 원격 제어 등 다양한 네트워크 작업에 사용할 수 있다. Tailscale 네트워크 내의 다른 장치에서도 이 주소를 통해 해당 서버에 직접 접근할 수 있다.


가장 맘에 드는 기능이다. 제로트러스트 메쉬 네트워크 상에서 자동 TLS(ACME 기반) 를 제공하여, 내부 IP만 가진 서비스도 공개 인증서 기반 HTTPS를 사용할 수 있게 한다.
your-server$
your-server$
your-server$ sudo tailscale cert yourserver.xxxxxxx.ts.net
# Wrote public cert to yourserver.xxxxxxx.ts.net.crt
# Wrote private key to yourserver.xxxxxxx.ts.net.key
your-server$
your-server$
your-server$ sudo mkdir -p /etc/ssl/certs /etc/ssl/private
your-server$
your-server$
your-server$ sudo mv yourserver.xxxxxxx.ts.net.crt /etc/ssl/certs/
your-server$ sudo mv yourserver.xxxxxxx.ts.net.key /etc/ssl/private/
your-server$
your-server$
your-server$ sudo chmod 644 /etc/ssl/certs/yourserver.xxxxxxx.ts.net.crt
your-server$ sudo chmod 600 /etc/ssl/private/yourserver.xxxxxxx.ts.net.key
your-server$
your-server$
your-server$ sudo vim /etc/nginx/conf.d/mapandnote-web.conf
# server {
# listen 443 ssl;
# server_name yourserver.xxxxxxx.ts.net;
# ssl_certificate /etc/ssl/certs/yourserver.xxxxxxx.ts.net.crt;
# ssl_certificate_key /etc/ssl/private/yourserver.xxxxxxx.ts.net.key;
# location / {
# proxy_pass 여기는 기존 서버와 연결하는 방식(socket 등등)
# 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;
# }
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data:; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self'; frame-ancestors 'self'; base-uri 'self';" always;
# }
your-server$
your-server$
your-server$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
your-server$
your-server$
your-server$ sudo systemctl reload nginx
your-server$ sudo systemctl restart nginx
your-server$
your-server$
your-server$
your-server$ sudo vim /etc/systemd/system/tailscale-cert-renew.service
# [Unit]
# Description=Renew Tailscale TLS certificate
# After=network-online.target
# Wants=network-online.target
# [Service]
# Type=oneshot
# ExecStart=/usr/bin/tailscale cert \
# --cert-file /etc/ssl/certs/yourserver.xxxxxxx.ts.net.crt \
# --key-file /etc/ssl/private/yourserver.xxxxxxx.ts.net.key \
# yourserver.xxxxxxx.ts.net
# # 인증서가 갱신된 뒤 Nginx reload
# ExecStartPost=/bin/systemctl reload nginx
your-server$
your-server$
your-server$
your-server$ sudo vim /etc/systemd/system/tailscale-cert-renew.timer
# [Unit]
# Description=Run Tailscale TLS cert renew daily
# [Timer]
# OnCalendar=daily
# Persistent=true
# [Install]
# WantedBy=timers.target
your-server$
your-server$
your-server$
your-server$ sudo systemctl daemon-reexec
your-server$ sudo systemctl enable --now tailscale-cert-renew.timer
your-server$
your-server$
your-server$ systemctl list-timers tailscale-cert-renew.timer
your-server$ journalctl -u tailscale-cert-renew.service
add_header Content-Security-Policy를 한 줄로 작성하지 않을 경우, next.js에서 향후 에러가 발생한다.
The only way to fix this is for the developer of the API at ooooo.xxxxx.ts.net to correct the malformed Content-Security-Policy header. The server must send that header's value as a single, continuous line. Until they fix their server's response, your Next.js application will be unable to correctly process a successful response from that endpoint.