cretueusebiu/valet-windows
Valet for Windows brings the Laravel Valet experience to Windows: a fast, minimal local development environment powered by Nginx and PHP. Easily serve Laravel and other PHP sites, manage parked and linked paths, and use simple CLI commands instead of configuring a full stack manually.
This is not a Composer library — treat it as a machine-level tool. You never add cretueusebiu/valet-windows to an application's composer.json. It is a global CLI that owns Nginx, the hosts file, and TLS certs on your Windows box. That framing matters: the "integration" surface is your workstation, not your codebase, and the risk profile is "does it conflict with my other local tooling," not "does it break the build."
Prerequisites, in order (the order is the trap):
PATH. Valet drives the php on your PATH; it does not ship its own binary on a fresh install.php.ini with at minimum openssl, mbstring, fileinfo, curl, pdo_mysql, and a valid extension_dir. Composer will fail silently-ish without openssl.%APPDATA%\Composer\vendor\bin on PATH so the valet shim resolves.valet share and general sanity).Install:
composer global require cretueusebiu/valet-windows
valet install
Run that second command from an elevated PowerShell. valet install writes to C:\Windows\System32\drivers\etc\hosts, binds ports 80/443, and registers services. Non-elevated runs fail in confusing ways — the command reports success but *.test never resolves.
First use case (5 minutes to a served Laravel app):
mkdir C:\Users\me\Projects
cd C:\Users\me\Projects
valet park
composer create-project laravel/laravel blog
# browse http://blog.test
park makes every immediate subfolder of the current directory serve at <foldername>.test — forever, with no per-project config. That single command is 90% of the day-to-day value.
Where to look first when something is wrong. The install prints the working directory (typically C:\Users\<you>\.config\valet). Learn these four subpaths immediately:
| Path | What lives there |
|---|---|
config.json |
parked paths, linked sites, TLD, PHP version |
Nginx\ |
generated per-site vhosts + the global nginx.conf |
Certificates\ |
certs produced by valet secure |
Log\ |
Nginx error.log / access.log — first stop for any 502/404 |
Park vs. link — pick a default and stick to it. Park your aggregate project root once (valet park in C:\Users\me\Projects). Use valet link <name> only for outliers — a repo living on D:\, a worktree, a client handoff folder you don't want in the default namespace. Audit with valet paths and valet links; prune with valet forget (removes a parked path) and valet unlink (removes a linked site). Teams that mix both indiscriminately end up with duplicate hostnames and a vhost pointing at the wrong tree.
The daily loop:
cd C:\Users\me\Projects\blog
valet links # confirm what's served
valet restart # after any nginx.conf or PHP change
valet secure blog # https://blog.test, once, committed to muscle memory
Database per project, not per machine. MySQL/MariaDB runs on localhost:3306 with root and an empty password by default — fine for local, but make it a habit to create a named schema per project (blog, blog_testing) and put it in .env immediately after create-project. Never let two projects share a schema; the test suite will truncate your demo data.
Mail: point everything at MailHog and stop thinking about it. In .env:
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
Inspect at http://localhost:8025. This is the single biggest reason to keep Valet running rather than switching mailers per project.
Redis for queues/cache, with a Windows caveat. If php_redis.dll isn't loaded in php.ini, Laravel's default predis fallback will bite you at runtime. Either install the DLL matching your PHP build (TS/NTS and version must match exactly) or set REDIS_CLIENT=predis in .env and add predis/predis. Decide once per machine.
Long-running processes have no supervisor. For queues use php artisan queue:work in its own terminal during dev, and php artisan queue:listen when actively editing job classes (it reloads). For production-like persistence, register the command with Task Scheduler or NSSM — don't leave a queue:work in a tab you'll close.
Scheduler: add a Task Scheduler entry running php artisan schedule:run every minute per project. This is the Windows equivalent of the cron line in Laravel docs and is easy to forget until a nightly job silently never fires.
valet share for client demos. Link the site first (valet link), cd into it, then valet share. You get a public https URL tunnelled to the local vhost. Useful for reviewing responsive work on a real phone against real .test data.
Per-site Nginx extension point. When you need websockets, a custom header, a larger client_max_body_size, or a PHP fastcgi_pass tweak, drop a <site>.test.conf into ~/.config/valet/Nginx\ (copy the generated one and edit) then valet restart. Use forward slashes in root regardless of Windows paths. Keep the edits in a dotfiles repo — valet regenerates these on reinstall.
Vite/HMR against .test. npm run dev binds localhost:5173; the page at blog.test must reach it. Usually works because .test resolves to loopback, but if HMR stalls, set in vite.config.js:
server: { host: 'blog.test', hmr: { host: 'blog.test' } }
Under valet share the tunnel only forwards the app port — HMR breaks, so run npm run build before demoing.
Onboarding script for the team. Keep a setup.ps1 that installs PHP, Composer, and valet, runs valet install, valet park, and seeds the shared DB. Valet is only zero-config after the one-time machine setup; encoding the setup once is what makes it actually zero-config for the next hire.
Elevation is the #1 silent failure. valet park, valet link, valet secure, and valet unsecure all mutate the hosts file. Without admin, they appear to succeed but the entry is never written, giving you a DNS-failure page rather than an error message. If a new site won't resolve but valet links lists it, check the hosts file before anything else.
Port 80/443 is almost certainly already taken on a dev's machine. Usual suspects: IIS (W3SVC / "World Wide Web Publishing Service"), http.sys consumers (SQL Server Reporting Services, BranchCache, some print services), old Skype installs, and Docker Desktop if it's publishing 80. Stop the service, don't fight it. netstat -ano | findstr :80 then map the PID in Task Manager.
New project folders inside a parked path are not automatically resolvable. Windows' hosts file has no wildcard support, so Valet writes concrete entries per site. After git clone-ing a new app into a parked root, re-run valet park (or valet link the specific folder) so the hostname gets an entry. This trips people up constantly because macOS Valet's dnsmasq wildcard makes it unnecessary there.
Symlinks need Developer Mode. valet link relies on creating links in the Sites directory. Enable Settings → Privacy & Security → For developers → Developer Mode (or run elevated), otherwise linking fails with a permissions error that reads like a PHP bug.
Never use .dev or .app as your TLD. Both are HSTS-preloaded — the browser forces HTTPS to Google-owned infrastructure and your local site becomes unreachable with no obvious cause. .test is RFC 6761 reserved for exactly this and is the default; keep it.
502 Bad Gateway means PHP, not Nginx. Look at Log\error.log first: connection refused on the fastcgi port means the PHP/FPM service is down or the port in nginx.conf doesn't match the running PHP. valet restart fixes the common case; if it doesn't, valet services/valet status to see which service is dead, and check that no other PHP install (Laragon, XAMPP) grabbed the port.
Don't run Valet alongside XAMPP/Laragon/WAMP. They all want 80, 443, 3306, and 6379, and each one rewrites the hosts file and services. Pick one per machine. Similarly, Docker Desktop should not be publishing 80/443.
Keep projects off OneDrive / Dropbox-synced folders and paths with spaces or non-ASCII characters. Placeholder files, file locking, and path rewriting break Composer installs, Nginx root directives, and valet link in ways that look like unrelated PHP errors. Use a plain C:\Users\me\Projects.
Xdebug costs you 2–5× request time across the whole environment. Set xdebug.mode=off by default and flip it on only while debugging; leaving develop,debug on means every Valet site feels slow and you'll blame Nginx.
valet share needs a real ngrok account now. Newer ngrok versions require an authtoken and show a free-tier interstitial to visitors. Fine for a demo, not for a client review you care about — say so up front, or use a paid tunnel.
Certificate trust is not fully automatic. valet secure generates the cert into Certificates\, but browsers may still warn. Import it into Trusted Root Certification Authorities via certlm.msc (Local Machine). Also remember valet unsecure when you revert to HTTP — stale HSTS or cached redirects will keep pushing you to https and confuse the next debug session.
Every config edit needs valet restart. Nginx does not hot-reload here. Editing a generated vhost and wondering why nothing changed is a self-inflicted 20-minute detour.
PHP version switching is version-sensitive. 2.5.3 added PHP 8.4 support; the valet use [email protected]-style command downloads a matching build. Confirm with php -v in the same shell afterwards — a stale PATH entry pointing at an older C:\php will make it look like the switch failed while Nginx happily serves a different version than your CLI. Verify both php -v and phpinfo() through a .test URL when it matters.
Windows Firewall will prompt on first Nginx start. Allow it for private networks; denying it once means everything works until you try valet share or hit the site from another device.
Recovery when things get truly wedged: valet stop, delete ~/.config/valet/Nginx\*.conf and the stale hosts entries for your TLD, then valet install followed by re-park. Keep your parked paths documented — the reinstall is fast, re-registering eight sites from memory is not.
Windows alternatives, for context: if the team leans Linux/Docker, WSL2 + Laravel Sail or DDEV is a more portable answer. Valet Windows wins when the requirement is "native Windows, no VM, no Docker, .test domains, one command" — which is exactly its stated niche and why it stays small.
How can I help you explore Laravel packages today?