So here's the thing. I've been building a Laravel + Inertia + React project and deploying it on a VPS. My workflow was:
npm run build
php artisan serve --host 0.0.0.0
The problem
When you run npm run dev, Vite starts a dev server and a WebSocket for HMR. By default it binds to localhost — meaning only processes on the same machine can reach it. The browser, sitting on your laptop, can't connect.
But Vite has a server.host option that makes it bind to all interfaces, including the public one. And a server.hmr.host option that tells the browser where to connect for the WebSocket. That's the key piece most tutorials miss.
Step 1 — update vite.config.ts
Add a server block to your existing config. Everything else stays the same:
import { wayfinder } from '@laravel/vite-plugin-wayfinder';
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import laravel from 'laravel-vite-plugin';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.tsx'],
ssr: 'resources/js/ssr.tsx',
refresh: true,
}),
react({
babel: { plugins: ['babel-plugin-react-compiler'] },
}),
tailwindcss(),
wayfinder({ formVariants: true }),
],
esbuild: { jsx: 'automatic' },
// 👇 add this block
server: {
host: '0.0.0.0',
port: 5173,
hmr: {
host: 'YOUR_VPS_IP', // e.g. '123.123.123.123'
port: 5173,
},
},
});
Why
hmr.hostmatters: Without it, the browser tries to open the WebSocket onlocalhost— which is your laptop, not the VPS. It silently fails and you get no live updates.
Step 2 — update .env
The Laravel Vite plugin reads APP_URL to know where the backend is. Set it to your real VPS IP and port:
APP_URL=http://YOUR_VPS_IP:6040 # public ip of the vps
You'll know it's working when npm run dev shows:
➜ APP_URL: http://YOUR_VPS_IP:6040 # public ip of the vps
Step 3 — open the ports on your firewall
This is where I got stuck the longest. UFW alone isn't enough if you're on a cloud provider.
OS firewall (UFW):
sudo ufw allow 5173
sudo ufw allow 6040
sudo ufw reload
Cloud firewall (Hetzner / DigitalOcean / AWS):
Most VPS providers have a separate cloud-level firewall that sits in front of your server. UFW never even sees the traffic. You need to open the port there too.
For Hetzner: go to console.hetzner.cloud → Firewalls → your firewall → Add inbound rule → TCP port 5173.
💡 Security tip: Instead of allowing
0.0.0.0/0, restrict port 5173 to your own IP only. The dev server has no auth — you don't want it public.
Step 4 — run both servers
Open two terminal sessions on your VPS (or use tmux):
# terminal 1 — Laravel
php artisan serve --host 0.0.0.0 --port 6040
# terminal 2 — Vite
npm run dev
Summary
| What | Command | Port |
|---|---|---|
| Laravel backend | php artisan serve --host 0.0.0.0 | 6040 |
| Vite HMR dev server | npm run dev | 5173 |
Edit the vite.config.json
Now open http://YOUR_VPS_IP:6040 in your browser, edit a React component, and watch it update instantly. No rebuild. No refresh. Full HMR on a remote VPS.
Read more
Laravel `php artisan serve` Fails on Windows (Ports 8000–8010) - Here is a quick fix
Failed to listen on 127.0.0.1:8000 Failed to listen on 127.0.0.1:8001
How to Add Swap Space on Ubuntu (5GB Example)
A step-by-step guide to creating a swap file on Ubuntu, making it persistent across reboots, and tuning swappiness for better performance.
How to Access Your Ubuntu VPS from a Browser Using Chrome Remote Desktop (LXDE)
If you manage a Ubuntu VPS, sometimes SSH is not enough. You may want a full desktop environment accessible directly from your browser - just like using a normal computer.
