Back to blog

Remote Development

John Chynoweth··3 min read
Tailscale Mosh AWS

I want to run a remote server to develop my Astro blog website and host my AI agents. I am using an Amazon EC2 instance that I shut down when it is not in use. Because of this, the server does not have a dedicated or static public IP address. To connect easily every time it boots, I use Tailscale.

How the Tailscale Mesh Network Helps:

  • The MagicDNS System: Tailscale runs a private phonebook for all connected computers. Whenever my EC2 server turns on, it tells Tailscale its new, hidden location. Tailscale instantly updates my special .ts.net domain name to point directly there. [1]

  • The Overlay Network: Tailscale builds a secure, private tunnel between my home computer and my EC2 server. I do not even need to open any public firewalls or ports on Amazon AWS to get inside. A Tailscale network (known as a tailnet) contains different types of names used to identify and connect everything within it. Each name serves a specific purpose, establishes the network’s identity and namespace, and ensures devices are reachable using MagicDNS. [2] There are three primary types of names in a tailnet:

    1. Tailnet DNS name: The Fully Qualified Domain Name (FQDN) used for name resolution throughout the tailnet.
    2. Machine name: The specific name assigned to a device or node in the tailnet.
    3. Tailnet ID: The string used to identify the network when interacting with the Tailscale API. [1, 3]

For more details, see the official Tailscale Tailnet Name Documentation. [1]

Bash Script to Start and Connect to the EC2 Server

I use this local automation script to boot my instance and securely establish a remote terminal connection using Mosh over Tailscale:

#!/bin/bash
# Configuration Variables
INSTANCE_ID="[ec2 instance id]"
REGION="us-east-1"
KEY_PATH="somepemfile.pem"
USER_NAME="ubuntu"
TAILSCALE_DOMAIN="ip-[tailscale ip address].tailf11a29.ts.net"

echo "Starting EC2 Instance: $INSTANCE_ID in $REGION..."
aws ec2 start-instances --instance-ids "$INSTANCE_ID" --region "$REGION" > /dev/null

echo "⏳ Waiting for instance to fully boot up..."
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" --region "$REGION"
# Short pause to let the Tailscale service wake up on the server
echo "Waiting a brief moment for Tailscale connection..."
sleep 3

echo "Connecting via MOSH to: $USER_NAME@$TAILSCALE_DOMAIN..."
mosh --ssh="ssh -i $KEY_PATH" "$USER_NAME@$TAILSCALE_DOMAIN"

2. Running the Web Application

My blog is built as an Astro web application. When I launch the development server on the remote machine using pnpm dev, it runs locally on port 4321 (accessible via localhost:4321). Because I want to view the site securely across my private network without exposing raw ports, I use the Caddy web server as a reverse proxy. [4]

Caddy Reverse Proxy Configuration

I update my configuration file located at /etc/caddy/Caddyfile with the following block:

ip-[tailscale ip address].tailf11a29.ts.net {
    reverse_proxy 127.0.0.1:4321
}
  • Starting the Services To put everything into action, I start my Astro development server and reload Caddy to apply the new reverse proxy rules: [5]

  • Start the Astro development project ‘pnpm dev’

  • Reload Caddy without downtime to route the traffic ‘sudo systemctl reload caddy’

-Verifying and Checking the App Once everything is up and running, I can manage my endpoints and securely preview the live development environment using these administrative links:

[1] https://tailscale.com [2] https://tailscale.com [3] https://tailscale.com [4] https://swetrix.com [5] https://oneuptime.com