localhost:3000. 127.0.0.1:8000. localhost:5173. Every project gets a random port, you forget which is which, and then things get weird: you’re logged into the wrong app, cookies from one project leak into another, and OAuth callbacks break because the port changed.
There’s a better way: give every project its own domain, like https://shop.test.
Why ports are a problem
- Cookies are shared per host, not per port. Two apps on
localhost:3000andlocalhost:8000see each other’s cookies. Log into one, get logged out of the other. - Ports change. Vite picks 5174 if 5173 is busy. Your OAuth app, CORS rules and
.envstill say 5173. - No HTTPS. Secure cookies, service workers, the clipboard API and many payment and login SDKs need a secure context. (Browsers make an exception for
localhost, but not for your phone or a colleague.) - It doesn’t look like production. Absolute URLs, sub-domains (
api.shop.test) and multi-tenant apps are hard to test on a port.
How local domains work
Three pieces make https://shop.test work on your machine:
- Name resolution. The hosts file (
C:\Windows\System32\drivers\etc\hostsor/etc/hosts) mapsshop.testto127.0.0.1. - A web server on port 80/443 that knows which project belongs to
shop.test(a virtual host). For a PHP project it serves the files; for a Node project it proxies to the dev server’s port. - A trusted certificate for
shop.test, so HTTPS has no warnings. See My site needs SSL.
Why .test? It’s reserved for testing and will never exist on the internet. Avoid .dev (a real domain that browsers force onto HTTPS) and .local (used by mDNS).
Doing it by hand
For one PHP project with Apache: add 127.0.0.1 shop.test to the hosts file, write a <VirtualHost> with a DocumentRoot, generate and trust a certificate, add an SSL virtual host, and restart Apache. For a Node app, add ProxyPass / http://127.0.0.1:3000/ (plus WebSocket rules for hot reload). Then repeat for every project, and update it every time a port changes.
Doing it with LampRabit
Put the project in the www folder. That’s it:
- PHP projects are served at
https://<folder>.test, frompublic/when there is one. - Node.js projects (Vite, Next.js, Nuxt, Astro, Express, NestJS) get their domain too. Start the dev server from LampRabit, or run
npm run devin your own terminal: LampRabit watches which port the project’s process listens on and points the domain (and its certificate) there within a few seconds. - When the dev server stops, the domain falls back to the project’s files (its build folder, for example).
- Sub-domains work:
api.shop.testis served byshop. - The hosts file is updated for you, with one administrator prompt when the list changes. Prefer no hosts file at all? Set the suffix to
.localhost, which browsers resolve to your machine on their own.
cd ~/lampRabit/www/dashboard
npm run dev # Vite picks port 5173…
# …and https://dashboard.test now shows it, hot reload included.
The payoff
One URL per project, forever. Cookies stay separate. HTTPS everywhere. And you never have to remember whether the admin panel was on 3001 or 3002 again.
Download LampRabit, free for Windows and Linux (macOS in preview).
Frequently asked questions
Why use .test instead of .local or .dev?
.test is reserved for testing (RFC 2606), so it will never be a real website. .dev is a real top-level domain that browsers force onto HTTPS, and .local is used by mDNS/Bonjour, which can slow down lookups.
Does a .test domain work for Next.js or Vite dev servers?
Yes, through a reverse proxy: the web server receives https://app.test and forwards to the dev server's port. LampRabit does this automatically and follows the port the dev server actually listens on.
Can I avoid editing the hosts file?
Yes. Names ending in .localhost resolve to your own computer in modern browsers without any hosts entry. LampRabit can use .localhost as its domain suffix.



