How to Set Up Tailscale + Apache Guacamole on Oracle Cloud for Browser-Based Remote Desktop

# How to Set Up Tailscale + Apache Guacamole on Oracle Cloud for Browser-Based Remote Desktop

Date: 2026-07-14

## Why This Stack

You want to remote desktop into your home machines from a browser. No VPN client, no port forwarding, no TeamViewer "commercial use" lockouts. Here is the stack that works:

- **Oracle Cloud free tier ARM instance** — public IP, always free, 4 cores / 24 GB RAM
- **Tailscale** — mesh VPN. All your machines connect to Tailscale. No open ports.
- **Apache Guacamole** — clientless RDP/VNC/SSH gateway. Runs as a Docker container.
- **Nginx Proxy Manager** — TLS termination, Let's Encrypt SSL, reverse proxy rules.
- **Authentik** — SSO forward-auth (optional but recommended: no one reaches Guacamole without logging in first).

The flow: browser → `rdp.yourdomain.com` → NPM (TLS) → Authentik (auth) → Guacamole → Tailscale → your internal machine.

## Step 1: Oracle Cloud Free Tier Instance

Sign up at cloud.oracle.com. Use the "Always Free" ARM Ampere A1 instance:

```
Shape: VM.Standard.A1.Flex
OCPU: 4
Memory: 24 GB
Boot volume: 100 GB
OS: Ubuntu 22.04 LTS
```

After provisioning, assign a public IP (Ephemeral or Reserved). Note it — this is your entry point.

### Initial firewall

Oracle uses Security Lists, not iptables. In the OCI console, add ingress rules:

| Source | Protocol | Port | Purpose |
|---|---|---|---|
| 0.0.0.0/0 | TCP | 80 | NPM HTTP |
| 0.0.0.0/0 | TCP | 443 | NPM HTTPS |
| 0.0.0.0/0 | TCP | 81 | NPM admin (restrict to your IP later) |

Do NOT open 22 to the world. SSH access goes through Tailscale only (Step 2).

## Step 2: Install Tailscale on Every Machine

On the Oracle instance:

```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --accept-routes --accept-dns
```

On every internal machine you want to remote into (Windows desktop, Linux server, Raspberry Pi):

```bash
# Same install command
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
```

Now lock down SSH on Oracle to Tailscale only. Create `/etc/ssh/sshd_config.d/99-tailscale.conf`:

```
Match Address 100.0.0.0/8
    PasswordAuthentication yes
Match all
    PasswordAuthentication no
```

This means: SSH from Tailscale IPs (100.x.x.x) is allowed. Everything else is rejected. Your public IP still shows port 22 closed — Oracle's security list already blocks it.

### Verify

From your laptop (connected to Tailscale):

```bash
ssh ubuntu@100.87.69.61   # Oracle's Tailscale IP
```

From outside Tailscale:

```bash
ssh ubuntu@192.9.163.106   # Oracle's public IP — blocked
```

## Step 3: Nginx Proxy Manager

On Oracle, create a docker-compose for NPM:

```yaml
# stacks/nginx-proxy-manager.yml
version: '3'
services:
  nginx-proxy-manager:
    image: jc21/nginx-proxy-manager:2.10.3
    container_name: npm
    restart: unless-stopped
    ports:
      - "80:80"
      - "81:81"
      - "443:443"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
```

```bash
docker compose -f stacks/nginx-proxy-manager.yml up -d
```

Access NPM admin at `http://:81`. Default login: `admin@example.com` / `changeme`. Change immediately.

### DNS setup

Create an A record pointing `rdp.yourdomain.com` to your Oracle public IP. (For the rest of this guide, substitute your actual domain.)

### SSL certificate

In NPM → SSL Certificates → Add Let's Encrypt → enter `rdp.yourdomain.com`. NPM handles renewal automatically.

## Step 4: Apache Guacamole

Guacamole has two components: `guacd` (the backend daemon) and `guacamole` (the web frontend). Run both on Oracle:

```yaml
# stacks/guacamole.yml
version: '3'
services:
  guacd:
    image: guacamole/guacd:1.5.5
    container_name: guacd
    restart: unless-stopped
    volumes:
      - ./guacd/drive:/drive
      - ./guacd/recordings:/recordings

  guacamole:
    image: guacamole/guacamole:1.5.5
    container_name: guacamole
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      GUACD_HOSTNAME: guacd
      MYSQL_HOSTNAME: guacamole-db
      MYSQL_DATABASE: guacamole_db
      MYSQL_USER: guacamole_user
      MYSQL_PASSWORD: CHANGE_ME
    depends_on:
      - guacd
      - guacamole-db

  guacamole-db:
    image: mysql:8.0
    container_name: guacamole-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: CHANGE_ME_ROOT
      MYSQL_DATABASE: guacamole_db
      MYSQL_USER: guacamole_user
      MYSQL_PASSWORD: CHANGE_ME
    volumes:
      - ./guacamole-db:/var/lib/mysql
```

Note: Guacamole only listens on `127.0.0.1:8080` — not exposed to the internet. NPM proxies to it.

### Initialize the database

```bash
docker run --rm guacamole/guacamole:1.5.5 /opt/guacamole/bin/initdb.sh --mysql > initdb.sql
# Import into the guacamole-db container
```

### Add your connections

In the Guacamole web UI (once proxied), add connections pointing to Tailscale IPs:

| Connection name | Protocol | Host | Port |
|---|---|---|---|
| Dev Desktop | RDP | 100.x.x.x (your desktop's Tailscale IP) | 3389 |
| Ubuntu Server | SSH | 100.89.58.49 | 22 |
| Home Laptop | VNC | 100.x.x.x | 5900 |

Because everything is on the Tailscale network, Guacamole on Oracle can reach any machine with a Tailscale IP. No port forwarding, no dynamic DNS.

## Step 5: Authentik SSO (Optional but Recommended)

Without Authentik, anyone who finds your Guacamole URL sees the login page. With Authentik, they don't even reach Guacamole — they get redirected to an SSO login first.

```yaml
# Include in existing Authentik stack or create standalone
services:
  authentik-server:
    image: ghcr.io/goauthentik/server:2024.8
    container_name: authentik-server
    # ... full Authentik config
```

In NPM → Proxy Hosts → `rdp.yourdomain.com` → Advanced:

```
location / {
    # Forward-auth to Authentik
    auth_request /outpost.goauthentik.io/auth/nginx;
    error_page 401 = @goauthentik_proxy_signin;

    # If auth passes, proxy to Guacamole
    proxy_pass http://127.0.0.1:8080/guacamole/;
}

location /outpost.goauthentik.io/ {
    proxy_pass http://authentik-server:9000/outpost.goauthentik.io/;
}
```

Result: user opens `rdp.yourdomain.com` → Authentik login screen → if valid, page loads Guacamole. No Guacamole login page is ever exposed to the internet.

## Step 6: Access From Anywhere

On any device with a browser:

1. Open `https://rdp.yourdomain.com`
2. Sign in with Authentik (if configured) or Guacamole credentials
3. Select your connection from the Guacamole dashboard
4. Desktop appears in the browser — fully interactive, no client install

Works on phones, tablets, locked-down work laptops, library computers. All you need is a browser.

## Total cost

| Component | Cost |
|---|---|
| Oracle Cloud ARM instance | $0 (Always Free tier) |
| Domain (yourdomain.com) | ~$15/year |
| Tailscale (personal, up to 100 devices) | $0 |
| Guacamole, NPM, Authentik | $0 (open source) |
| **Total** | **~$15/year** |

## Why not alternatives?

| Alternative | Problem |
|---|---|
| Tailscale Funnel | Exposes internal services directly to internet. No auth layer. Limited to 3 ports. |
| Chrome Remote Desktop | Chrome must be running. No Linux server support. Google account dependency. |
| TeamViewer | Expensive. Flags personal use as "commercial" randomly. Proprietary. |
| Plain RDP port forward | Dangerous. RDP has a massive attack surface. Brute-force magnet. |

## Troubleshooting

**Guacamole connection fails:** verify Tailscale is running on both Oracle and the target machine (`tailscale status`). Ping the Tailscale IP from Oracle. If it fails, check `tailscale up --accept-routes` was used.

**NPM returns 502:** Guacamole container not reachable at `127.0.0.1:8080`. Check `docker ps` — is `guacamole` running? Check logs: `docker logs guacamole`.

**Authentik returns 404:** outpost not configured. Verify the Authentik outpost is set to proxy mode and the provider is linked to the application.

**SSH blocked from public IP:** working as designed. Use the Tailscale IP.

Why This Stack?

For the architectural reasoning behind each component choice — why Oracle over AWS, why Tailscale over plain WireGuard, why Guacamole over RDP — see the companion article: Reliable Remote Desktop at Home with Tailscale and Guacamole.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *