DEV Community

Cover image for Code PHP From Your Phone: The VPS + Tmux + Termius Setup That Actually Works
Hafiz
Hafiz

Posted on • Originally published at hafiz.dev

Code PHP From Your Phone: The VPS + Tmux + Termius Setup That Actually Works

Originally published at hafiz.dev


I've been running this setup on a Hetzner server for a while. This post documents exactly how I set it up, including the mistakes I made along the way that most tutorials skip over. I went through the full setup live while writing this, so the gotchas are real.

If you haven't secured the VPS itself yet, start with how I hardened mine with SSH, Cloudflare, and Tailscale first. That post covers locking down SSH, hiding your server IP, and routing access through Tailscale. This post covers what comes after: the PHP stack, Tmux sessions, Claude Code, and connecting from your phone.

Why a remote dev environment

Local dev has one fatal flaw. The moment your laptop closes, everything dies. Queue workers, running scripts, anything you left going. You come back and reconstruct context from scratch.

A VPS with Tmux flips this. Sessions persist indefinitely. You detach when you're done, reattach from any device and your work is exactly where you left it. The server never sleeps.

There's a practical benefit for anyone traveling or away from their desk. Your full dev environment runs on the server. Your local device is just a terminal window. An iPad, a phone, a borrowed laptop: they all get identical access to the same running environment.

For Laravel specifically: queue workers run continuously on the VPS. No need to restart them every session.

What this post covers

  • PHP 8.x + Composer already installed on the VPS? Skip Step 1.
  • Tmux already installed? Skip Step 2.
  • Just want the phone connection? Jump to Step 3.

Step 1: Check and install the PHP stack

First, see what's already on the server:

php -v 2>/dev/null || echo 'PHP: not installed'
composer -V 2>/dev/null || echo 'Composer: not installed'
tmux -V 2>/dev/null || echo 'Tmux: not installed'
which claude 2>/dev/null || echo 'Claude Code: not installed'
Enter fullscreen mode Exit fullscreen mode

If PHP is missing, install PHP 8.4 with the extensions a Laravel app needs:

sudo apt update && sudo apt upgrade -y
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.4 php8.4-cli php8.4-fpm php8.4-mbstring \
  php8.4-xml php8.4-curl php8.4-zip php8.4-mysql \
  php8.4-redis php8.4-bcmath php8.4-intl
Enter fullscreen mode Exit fullscreen mode

Install Composer if it's missing:

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
chmod +x /usr/local/bin/composer
Enter fullscreen mode Exit fullscreen mode

Install the Laravel installer globally:

composer global require laravel/installer
echo 'export PATH="$HOME/.config/composer/vendor/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

Set up Git with your details:

git config --global user.name "Your Name"
git config --global user.email "your@email.com"
Enter fullscreen mode Exit fullscreen mode

Step 2: Tmux for sessions that survive disconnects

Tmux is what makes this whole setup usable. Without it, every SSH disconnect kills your running processes. With it, sessions sit on the server indefinitely.

Install if needed:

sudo apt install tmux -y
Enter fullscreen mode Exit fullscreen mode

Add a session helper function to your .bashrc. This is the tm command you'll use every time you connect:

cat >> ~/.bashrc << 'EOF'

# Tmux session helper: creates or reattaches to a named session
tm() {
  local name="${1:-$(basename "$PWD")}"
  name="${name//\./-}"
  name="${name//\//-}"
  if [ -n "$TMUX" ]; then
    tmux has-session -t "$name" 2>/dev/null || tmux new-session -d -s "$name" -c "$PWD"
    tmux switch-client -t "$name"
  else
    tmux attach -t "$name" 2>/dev/null || tmux new -s "$name" -c "$PWD"
  fi
}
EOF
source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

Start a session:

tm dev
Enter fullscreen mode Exit fullscreen mode

You should see the Tmux status bar appear at the bottom of the terminal, showing the session name and timestamp. That bar is the confirmation that you're inside a persistent session.

For a Laravel project, I keep four windows in a single session:

View the interactive diagram on hafiz.dev

The Tmux commands you'll use daily:

Ctrl+B, D         # detach: leaves session running on server
Ctrl+B, C         # new window in current session
Ctrl+B, [number]  # switch to window 0, 1, 2...
Ctrl+B, ,         # rename current window
tmux ls           # list all sessions
tmux attach -t name  # reattach to a named session
Enter fullscreen mode Exit fullscreen mode

Critical gotcha: Always detach with Ctrl+B, D. Never close the terminal window or press Ctrl+C on the shell. That kills the session. Detach keeps it running on the server. This distinction is the entire point of Tmux.

For more on Laravel queue workers and why running them continuously matters, the queue jobs guide covers the production setup.

Step 3: Claude Code on the VPS

Install with the native installer (no Node.js required, and this is the recommended method as of 2026):

curl -fsSL https://claude.ai/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

Gotcha: The installer will warn you that ~/.local/bin is not in your PATH. It won't fix this for you. Run the fix manually:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

Verify it worked:

claude --version
# 2.1.150 (Claude Code)
Enter fullscreen mode Exit fullscreen mode

On first run, claude opens a browser authentication link. You need a Claude Pro ($20/month), Max, Team, Enterprise, or Console API account. The free tier doesn't include Claude Code access.

Start Claude Code in a dedicated Tmux window:

Ctrl+B, C         # new window
Ctrl+B, ,         # rename it "claude"
cd ~/my-laravel-app && claude
Enter fullscreen mode Exit fullscreen mode

Claude Code runs inside your project directory. It can read files, run artisan commands, write code, and manage git from the terminal. When you detach and reattach from any device, it's in the same state.

Step 4: Termius for phone access

Termius is the best SSH client for iOS and Android. It has a custom keyboard row with Ctrl, Esc, Tab, and pipe. The keys you actually need in a terminal. Plus a keep-alive setting that prevents the connection dropping when you switch apps.

Download from the App Store or Google Play.

Gotcha: create your account before adding anything. The free tier includes sync. If you add hosts and keys without logging in first, your local data may not transfer to your account when you sign up later. Open Termius, tap Profile, create the account, then proceed.

Add your SSH key:

  1. Tap Vaults → Keychain → +
  2. Choose Paste Key
  3. On your Mac, run cat ~/.ssh/id_rsa (or id_ed25519) and copy the entire output including the header and footer lines
  4. Paste into the Private Key field, give it a label like hetzner-key, save

SSH key saved in Termius Keychain

Add the host:

  1. Tap Vaults → Hosts → +
  2. Label: Hetzner Dev
  3. IP: your server's IPv4 address (get it with curl -4 ifconfig.me on the server)
  4. Username: root
  5. Tap Key, Certificate, FIDO2 → select hetzner-key
  6. Save, then tap the host to connect

Termius host configured with root username and hetzner-key

Once connected, reattach to your session:

tmux attach -t dev
Enter fullscreen mode Exit fullscreen mode

Tmux session running from iPhone showing the green status bar

The status bar reappears. Your queue worker, Claude Code session, and anything else you left running are all exactly where you left them.

Add a Startup Snippet to auto-attach on every connection:

In Edit Host, tap Startup Snippet and add:

tmux attach -t dev 2>/dev/null || tmux new -s dev
Enter fullscreen mode Exit fullscreen mode

Now every time you open Termius and tap the host, you land directly in your Tmux session. No typing required. On a phone keyboard that matters.

Bonus: the Termius AI Agent feature

When adding a host, Termius shows an "AI Agent" section suggesting setup for Claude Code, Gemini, and OpenCode. This is a Termius Pro feature that integrates AI assistants directly into the mobile terminal. If you're on Termius Pro, it connects to the Claude Code session running in your Tmux window. On the free tier, you don't need it; Claude Code works fine via regular SSH.

Proving it works

The test: create a session from your phone, start a long-running command, detach, reconnect, see it still running.

# From your phone, inside Tmux
echo "Connected from phone at $(date)" && sleep 9999
Enter fullscreen mode Exit fullscreen mode

Detach with Ctrl+B, D. Run tmux ls and the session shows as running. Reattach with tmux attach -t dev and your sleep is still going. Open the same session from your Mac and you'll see the exact same state: the same session name, the same running command.

Viewing the app from your phone browser

If you want to actually preview the Laravel app in a browser from your phone, php artisan serve alone won't work. It binds to 127.0.0.1:8000 on the VPS. Your phone's browser can't reach that address.

The clean solution: configure Caddy to serve the app on the server's Tailscale IP. Accessible from your phone's browser as long as Tailscale is running on both devices, invisible to the public internet.

# /etc/caddy/Caddyfile
http://100.x.x.x {
    root * /var/www/my-laravel-app/public
    php_fastcgi unix//run/php/php8.4-fpm.sock
    file_server
}
Enter fullscreen mode Exit fullscreen mode

For a coding and deployment workflow without browser preview, you don't need this. Claude Code on the VPS handles edits without a visual browser.

FAQ

Does this survive a VPS reboot?

Tmux sessions don't survive reboots. If the server restarts (for security updates, for example), your sessions are gone. Add a Supervisor config to restart queue workers and other persistent processes automatically on boot. Claude Code you restart manually when you reconnect.

What if I need to SSH from a machine without my key?

Use your VPS provider's browser console. Hetzner calls it the Console button in the server dashboard. It gives you terminal access without SSH. Bookmark it before you need it.

My phone keyboard can't send Ctrl+B properly. What do I do?

Use the Termius keyboard row above the standard keyboard. Tap ctrl, then tap b, then release. The modifier keys in Termius are designed for this. If you see ^B^B^B appearing as text, you're pressing ctrl and b simultaneously on the standard keyboard instead of using the Termius row.

Is a Hetzner VPS specifically required?

Any Ubuntu VPS works. DigitalOcean, Vultr, Linode: same commands, same setup. Hetzner is the one I use. The CX23 runs at €4/month with solid specs.

Do I need Tailscale for this to work?

Not for the basic setup. If port 22 is open on your server, Termius connects via the public IP. Tailscale is the right next step if you want to close port 22 and keep SSH off the public internet, which the VPS hardening guide covers.

The actual workflow

Once everything is running, the daily flow from your phone looks like this: open Termius, tap the host, land directly inside your Tmux session via the Startup Snippet. Queue worker is running. Claude Code is open in window 3. Your last git commit message is visible in window 0. Nothing to reconstruct.

The whole setup took about an hour to get right, including the debugging. The Hetzner server costs less than a coffee per month. And the next time your laptop battery dies mid-flight, your dev environment is fine.

If you build something with this setup and want to talk through the architecture, get in touch.

Top comments (0)