Google Antigravity provides an agentic pair-programming development environment. However, the standalone daemon binds strictly to localhost loopback interfaces and expects a local desktop session, lacking remote access capabilities out of the box.

This guide details how to configure a secure, persistent remote control setup for Google Antigravity on Linux using Caddy and Tailscale. By following these steps, you can access your Antigravity instance remotely over your private Tailnet from any desktop web browser or mobile phone.

Tip

While this post focuses on a Fedora environment, these instructions can just as easily be adopted into a standalone compose file or your environment.

The Network & Security Challenge

Exposing Antigravity's daemon remotely requires addressing three specific security and routing constraints:

  1. Host Header Validation & Passthrough Failure: Antigravity's backend daemon (language_server) enforces strict loopback origin checks (Host: 127.0.0.1:13306). tailscale serve proxies incoming traffic while preserving the original incoming Host header (node.tailnet.ts.net:39975). Because Tailscale Serve does not rewrite the Host header, direct requests fail with an Unauthorized Host (Localhost only) error. A reverse proxy capable of rewriting request headers (header_up Host ...) is required.
  2. Subpath Asset Mismatch: The embedded Web UI inside language_server requests static JavaScript and CSS assets using absolute root-relative paths (<script src="/main.js">, <link href="/compiled_tailwind.css">). Hosting Antigravity under a subpath like https://node.tailnet.ts.net/antigravity causes browsers to block assets due to Strict MIME Type Checking (X-Content-Type-Options: nosniff).
  3. Session Persistence: User systemd daemons automatically terminate upon SSH logout unless explicit session lingering is enabled.
Design Rationale (Dedicated Port vs. Tailscale Service)

To avoid hijacking standard HTTP/HTTPS port 443 on the host, we map Antigravity to a dedicated custom HTTPS port (39975) using Tailscale Serve. If custom ports are undesirable, an alternative clean approach is registering a dedicated Tailscale Service (svc:antigravity), which grants the service its own MagicDNS hostname (https://antigravity.tailnet.ts.net/).

System Architecture

The following sequence diagram illustrates how incoming HTTPS traffic flows from a remote client through Tailscale and Caddy to the local Antigravity daemon:

Diagram
Antigravity language_server (127.0.0.1:13306)Caddy Proxy (127.0.0.1:39975)Tailscale Serve (Port 39975)Antigravity language_server (127.0.0.1:13306)Caddy Proxy (127.0.0.1:39975)Tailscale Serve (Port 39975)Rewrite Host header to 127.0.0.1:13306Disable response buffering (flush_interval -1)Skip TLS verification on loopbackRemote Client (Browser / Mobile PWA)HTTPS GET https://node.tailnet.ts.net:39975/1HTTP GET http://127.0.0.1:39975/ (Host: node.tailnet.ts.net:39975)2HTTPS GET https://127.0.0.1:13306/ (Host: 127.0.0.1:13306)3200 OK (HTML / JS / CSS Assets)4200 OK5200 OK6Remote Client (Browser / Mobile PWA)

Step 1: Install Antigravity with Versioning

The following shell script fetches the latest Antigravity Linux release, extracts it cleanly into a versioned opt directory, establishes executable symlinks, and performs a runtime $PATH check.

BASH
#!/usr/bin/env bash
set -euo pipefail

# Configuration
OPT_DIR="${HOME}/.local/opt/antigravity"
BIN_DIR="${HOME}/.local/bin"
API_URL="https://antigravity-hub-auto-updater-974169037036.us-central1.run.app"

mkdir -p "${OPT_DIR}" "${BIN_DIR}"

# Fetch active release metadata
RELEASE_JSON=$(curl -sSLf "${API_URL}/api/v1/releases/latest?platform=linux&arch=x64")

if command -v jq >/dev/null 2>&1; then
  VERSION=$(echo "${RELEASE_JSON}" | jq -r '.version')
  DOWNLOAD_URL=$(echo "${RELEASE_JSON}" | jq -r '.url')
else
  VERSION=$(echo "${RELEASE_JSON}" | grep -oP '"version"\s*:\s*"\K[^"]+')
  DOWNLOAD_URL=$(echo "${RELEASE_JSON}" | grep -oP '"url"\s*:\s*"\K[^"]+')
fi

TARGET_DIR="${OPT_DIR}/${VERSION}"

if [ ! -d "${TARGET_DIR}" ]; then
  echo "Installing Antigravity v${VERSION}..."
  mkdir -p "${TARGET_DIR}"
  curl -sSLf "${DOWNLOAD_URL}" | tar -xz -C "${TARGET_DIR}" --strip-components=1
fi

# Update version symlinks
ln -sfn "${TARGET_DIR}" "${OPT_DIR}/current"
ln -sfn "${OPT_DIR}/current/resources/bin/language_server" "${BIN_DIR}/antigravity"

echo "Antigravity v${VERSION} installed successfully at ${OPT_DIR}/current."

# Verify executable PATH accessibility
if [[ ":${PATH}:" != *":${BIN_DIR}:"* ]]; then
  echo "Warning: ${BIN_DIR} is not in your current PATH."
  echo "Add 'export PATH=\"${BIN_DIR}:\${PATH}\"' to your ~/.bashrc or ~/.zshrc."
fi
PATH Environment Check

If ${HOME}/.local/bin was just created during execution, make sure to reload your shell environment (source ~/.bashrc) so the antigravity command is immediately accessible in your terminal.

Step 2: Configure Persistent Systemd Service & Lingering

To ensure Antigravity runs continuously without requiring an active desktop session, configure a user systemd service with environment substitution and enable session lingering.

Systemd Lingering Requirement

By default, systemd terminates user daemons upon SSH logout. Executing loginctl enable-linger $USER instructs systemd to boot user services automatically at startup and keep them running continuously.

Create ~/.config/systemd/user/antigravity-server.service using an environment variable placeholder:

INI
[Unit]
Description=Antigravity Daemon
After=network.target

[Service]
Type=simple
ExecStart=%h/.local/opt/antigravity/current/resources/bin/language_server \
  -standalone \
  -disable_telemetry=true \
  -https_server_port 39974 \
  -csrf_token ${ANTIGRAVITY_CSRF_TOKEN} \
  -app_data_dir %h/.config/antigravity \
  -api_server_url https://generativelanguage.googleapis.com \
  -cloud_code_endpoint https://cloudcode-pa.googleapis.com \
  -override_ide_name antigravity \
  -subclient_type hub \
  -override_ide_version ${ANTIGRAVITY_VERSION:-2.8.1} \
  -override_user_agent_name antigravity \
  -enable_sidecars
Restart=always
RestartSec=3s

[Install]
WantedBy=default.target

Generate a 32-byte hex secret and configure it into drop in file:

BASH
CSRF_TOKEN=$(openssl rand -hex 32)
sed -i "s/CHANGE_ME_CSRF_TOKEN/${CSRF_TOKEN}/g" ~/.config/systemd/user/antigravity-server.service
echo "Generated and injected CSRF Token: ${CSRF_TOKEN}"
BASH
cat <<EOF > ~/.config/systemd/user/antigravity-server.service.d/10-env.conf
[Service]
Environment="ANTIGRAVITY_CSRF_TOKEN=$(openssl rand -hex 32)"
EOF

# Secure permissions on the drop-in file
chmod 600 ~/.config/systemd/user/antigravity-server.service.d/10-env.conf

Enable and start the service along with user lingering:

BASH
loginctl enable-linger "${USER}"
systemctl --user daemon-reload
systemctl --user enable --now antigravity-server.service

Step 3: Configure Caddy Reverse Proxy

Caddy handles HTTPS termination, loopback TLS handshakes, and header overrides.

Ensure Caddy is installed, then create /etc/caddy/Caddyfile:

CADDY
:39975 {
    bind 127.0.0.1
    reverse_proxy https://127.0.0.1:39974 {
        transport http {
            tls_insecure_skip_verify
        }
        header_up Host 127.0.0.1:39974
        flush_interval -1
    }
}
Streaming Responses (flush_interval -1)

The flush_interval -1 directive disables response buffering in Caddy. This ensures WebSockets and Server-Sent Events (SSE) streaming tokens generated during agent completion calls reach the browser in real time without latency.

Restart Caddy:

BASH
sudo systemctl restart caddy

Step 4: Expose Port over Tailscale

Use Tailscale Serve to route traffic securely from your Tailnet to Caddy's loopback port:

BASH
tailscale serve --bg --https=39975 http://127.0.0.1:39975

Verify that Tailscale Serve is active:

BASH
tailscale serve status

Expected output:

TEXT
https://node.tailnet.ts.net:39975 (tailnet only)
|-- / proxy http://127.0.0.1:39975

Step 5: Mobile PWA Installation & Notifications

Once configured, access https://node.tailnet.ts.net:39975/ in your browser from any device connected to your Tailnet.

To convert the web interface into a standalone Progressive Web App (PWA):

  • Android (Google Chrome):

    1. Open https://node.tailnet.ts.net:39975/ in Chrome.
    2. Tap the menu icon (three dots) and select "Add to Home screen" or "Install app".
    3. Launch the app icon from your home screen and grant notification permissions when prompted.
  • iOS (Apple Safari):

    1. Open https://node.tailnet.ts.net:39975/ in Safari.
    2. Tap the Share button at the bottom of the screen.
    3. Scroll down and select "Add to Home Screen".
    4. Launch the application from your iOS home screen to run in standalone mode.

Technical Appendix: Why Subpath Proxying Fails

Subpath Proxying Pitfall

Attempting to serve Antigravity under a subpath (such as https://node.tailnet.ts.net/antigravity/) causes static asset loading to fail. The compiled frontend contains absolute references (/main.js, /compiled_tailwind.css). When a browser fetches /main.js from the domain root, Tailscale Serve returns an HTML fallback or 404 response. The browser detects that a script tag received non-JavaScript content and blocks execution due to Strict MIME Type Checking (X-Content-Type-Options: nosniff).

Using a dedicated HTTPS port (39975) or registering a dedicated Tailscale Service (svc:antigravity) preserves root path routing (/) for the web application while maintaining full isolation from host web servers running on port 443.