Fix: Vite server connection lost / HMR not working
Quick Answer
How to fix the '[vite] server connection lost. Polling for restart...' error and HMR not working. Covers file watcher limits, WebSocket config, Docker polling, WSL2, proxy forwarding, SSL issues, firewall blocking, and dependency pre-bundling.
The Error
You are working in your Vite project and suddenly the browser overlay shows:
[vite] server connection lost. Polling for restart...Or you see this in the browser console:
[vite] connecting...
[vite] server connection lost. Polling for restart...Sometimes HMR just silently stops working. You save a file, nothing updates, and the console shows:
[vite] connected.
[vite] server connection lost. Polling for restart...In some cases the page reloads entirely instead of performing a hot update, or the WebSocket connection drops repeatedly with:
WebSocket connection to 'ws://localhost:5173/' failedThis means Vite’s Hot Module Replacement (HMR) WebSocket connection between the browser and the dev server has broken. The browser can no longer receive update signals from Vite, so your changes do not appear until you manually refresh, and sometimes not even then.
The root causes range from OS-level file watcher limits to network configuration issues. Here are eight fixes, ordered from most common to most niche.
Why This Happens
Vite uses a WebSocket connection to push file change notifications from the dev server to the browser. When you save a file, Vite detects the change through your operating system’s file watcher (using chokidar under the hood), processes the update, and sends a message over the WebSocket telling the browser which module changed.
This pipeline can break at three points:
File watching fails — The OS does not notify Vite that a file changed. This happens when you exceed inotify limits on Linux, when the file system does not support native events (network drives, some Docker mounts, WSL2 cross-filesystem access), or when antivirus software intercepts file events.
WebSocket connection drops — The browser cannot maintain a persistent WebSocket to the Vite server. Proxies, firewalls, SSL mismatches, or incorrect
server.hmrsettings can all kill the connection.Server overload — Large dependency graphs or expensive pre-bundling can cause Vite to become unresponsive, dropping the WebSocket during long processing cycles.
Understanding which point in the chain is broken helps you pick the right fix.
Fix 1: Increase File Watcher Limits (Linux)
If you are on Linux and your project has many files, the operating system’s inotify watcher limit is the most common cause. When the limit is reached, Vite silently stops receiving file change events. You will see the connection lost message because Vite’s internal state goes stale.
Check your current limit:
cat /proc/sys/fs/inotify/max_user_watchesThe default on many distributions is 8192, which is not enough for a medium-sized project with node_modules.
Increase it temporarily:
sudo sysctl fs.inotify.max_user_watches=524288Make it permanent by adding the setting to your sysctl configuration:
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -pAfter this, restart Vite. If this was the problem, HMR reconnects immediately and stays stable.
Pro Tip: If you also hit the
ENOSPC: System limit for number of file watchers reachederror, the root cause is identical. See our full guide on fixing ENOSPC file watcher limits for additional details, including per-user limits and systemd overrides.
This fix only applies to Linux. macOS uses FSEvents (no inotify limit), and Windows uses ReadDirectoryChangesW (different constraints, covered in Fix 7).
Fix 2: Configure server.hmr in vite.config
When Vite’s default WebSocket settings do not match your environment, the HMR connection fails silently. This is especially common when your dev server binds to 0.0.0.0, runs behind a port-forwarded environment, or uses a non-standard port.
Open vite.config.ts (or vite.config.js) and explicitly set the HMR options:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
hmr: {
host: 'localhost',
port: 5173,
protocol: 'ws',
},
},
})Key properties:
host— The hostname the browser uses to connect to the WebSocket. Set this tolocalhostfor local development or your machine’s IP if accessing from another device.port— The WebSocket port. By default it shares the HTTP server port. Set it explicitly if a proxy is involved.protocol— Usewsfor HTTP orwssfor HTTPS. Mismatching this causes silent connection failures.
If you are accessing the dev server over your local network (e.g., from a phone or another machine), set host to your machine’s IP:
server: {
host: '0.0.0.0',
hmr: {
host: '192.168.1.100', // Your machine's LAN IP
},
},The server.host controls what the HTTP server binds to, while server.hmr.host controls where the browser sends the WebSocket connection. These often need to differ.
Fix 3: Fix Proxy or Reverse Proxy WebSocket Forwarding
If you run Vite behind Nginx, Apache, Caddy, or any reverse proxy, the proxy must explicitly forward WebSocket connections. Standard HTTP proxy rules do not cover WebSocket upgrades.
Nginx configuration:
location / {
proxy_pass http://localhost:5173;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}The critical lines are proxy_http_version 1.1, Upgrade, and Connection "upgrade". Without all three, Nginx downgrades the WebSocket handshake to a regular HTTP request and the connection fails.
Apache configuration:
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:5173/$1" [P,L]
ProxyPass / http://localhost:5173/
ProxyPassReverse / http://localhost:5173/Make sure mod_proxy_wstunnel is enabled:
sudo a2enmod proxy_wstunnel
sudo systemctl restart apache2If your proxy still does not forward WebSockets correctly, you can tell Vite to use a separate port for HMR that bypasses the proxy entirely:
server: {
hmr: {
port: 5174, // Different from the main server port
},
},This way the browser connects to the WebSocket directly on port 5174, sidestepping the proxy. This is a common workaround when you cannot modify the proxy configuration.
For related proxy troubleshooting, see fixing 502 Bad Gateway errors with Nginx.
Fix 4: Fix Docker and Container HMR (usePolling)
Docker containers with bind mounts do not propagate native file system events from the host to the container. Vite never sees your file changes, so HMR never fires and eventually the connection drops from inactivity.
Enable polling-based file watching in vite.config.ts:
export default defineConfig({
server: {
host: '0.0.0.0',
watch: {
usePolling: true,
interval: 1000,
},
hmr: {
host: 'localhost',
},
},
})usePolling: true tells chokidar to fall back to periodic stat checks instead of relying on OS file events. The interval value (in milliseconds) controls how often it checks. A value of 1000 (one second) balances responsiveness with CPU usage. Lower values like 100 make HMR faster but burn more CPU inside the container.
Also ensure your docker-compose.yml or docker run command exposes the Vite port:
services:
app:
ports:
- "5173:5173"
volumes:
- .:/appCommon Mistake: Setting
server.hosttolocalhostinside Docker makes Vite only listen on the container’s loopback interface. You must set it to'0.0.0.0'so the port mapping from the host actually reaches the server. This same issue affects any containerized dev server — see fixing ERR_CONNECTION_REFUSED on localhost for a broader explanation.
If you are using Docker Desktop on macOS or Windows with the gRPC FUSE or VirtioFS file sharing backend, native events might partially work. But polling is still the safest option for consistent HMR in containers.
Fix 5: Fix WSL2 File Watching Issues
Windows Subsystem for Linux 2 has a specific file watching problem: files stored on the Windows filesystem (under /mnt/c/, /mnt/d/, etc.) do not generate inotify events that WSL2 processes can see. If your project lives on a Windows drive but you run Vite inside WSL2, file changes are invisible to the server.
Solution 1: Move your project to the Linux filesystem.
Store your code under your WSL2 home directory (e.g., ~/projects/my-app) instead of /mnt/c/Users/.../my-app. File watching works natively on the ext4 filesystem inside WSL2.
# Copy project to Linux filesystem
cp -r /mnt/c/Users/yourname/projects/my-app ~/projects/my-app
cd ~/projects/my-app
npm install
npm run devThis is the recommended approach. Performance is dramatically better across the board, not just for file watching.
Solution 2: Enable polling (if you must stay on the Windows filesystem).
export default defineConfig({
server: {
watch: {
usePolling: true,
interval: 1000,
},
},
})This is the same polling workaround as the Docker fix. It works but costs extra CPU and introduces a slight delay before HMR triggers.
Solution 3: Use the --watch flag with WSL2’s native support (Windows 11+).
Recent Windows 11 builds improved cross-filesystem notification support. Make sure your WSL2 kernel is up to date:
wsl --updateThen restart WSL2 and test without polling. If HMR works, you can skip the polling overhead. If not, fall back to Solution 1 or 2.
Fix 6: Fix HTTPS/SSL Certificate Issues with HMR
When you run Vite with HTTPS (using @vitejs/plugin-basic-ssl or custom certificates), the HMR WebSocket must also use wss:// instead of ws://. If the certificate is self-signed or invalid, the browser may block the WebSocket connection without showing a visible error.
First, ensure the HMR protocol matches:
import basicSsl from '@vitejs/plugin-basic-ssl'
export default defineConfig({
plugins: [basicSsl()],
server: {
https: true,
hmr: {
protocol: 'wss',
},
},
})If you are using self-signed certificates, you need to explicitly trust them in your browser. In Chrome, navigate to https://localhost:5173 and click through the certificate warning before expecting HMR to work. The page itself may load despite the warning, but the WebSocket connection gets silently rejected.
For custom certificates (e.g., from mkcert):
import fs from 'fs'
export default defineConfig({
server: {
https: {
key: fs.readFileSync('./certs/localhost-key.pem'),
cert: fs.readFileSync('./certs/localhost.pem'),
},
hmr: {
protocol: 'wss',
},
},
})Using mkcert is the cleanest approach because it installs a local CA that your browser trusts automatically, eliminating the self-signed certificate problem entirely:
mkcert -install
mkcert localhost 127.0.0.1 ::1This generates localhost.pem and localhost-key.pem that browsers trust without manual intervention.
Fix 7: Fix Antivirus or Firewall Blocking WebSocket Connections
On Windows especially, antivirus software and firewalls can intercept or block the WebSocket connection between the browser and the Vite dev server. Windows Defender, Norton, Kaspersky, and other security suites sometimes flag rapid bidirectional localhost connections as suspicious.
Check if this is the cause:
- Temporarily disable your antivirus real-time protection.
- Restart Vite and test HMR.
- If it works, re-enable the antivirus and add an exclusion.
Add a Windows Defender exclusion for your project folder:
Add-MpPreference -ExclusionPath "C:\Users\yourname\projects\my-app"Or through the UI: Windows Security > Virus & threat protection > Manage settings > Exclusions > Add an exclusion > Folder.
Add a Windows Firewall rule for Node.js:
New-NetFirewallRule -DisplayName "Node.js Dev Server" -Direction Inbound -Program "C:\Program Files\nodejs\node.exe" -Action AllowOn Linux, check if ufw or iptables is blocking local connections:
sudo ufw statusIf the firewall is active, allow the Vite port:
sudo ufw allow 5173Note: Antivirus exclusions also improve general Vite performance. Security software scanning every file in node_modules on every change dramatically slows down both the dev server startup and file watching.
If you also see module resolution issues after fixing the connection, check our guide on Vite failed to resolve import errors for solutions.
Fix 8: Fix Large Dependency Pre-Bundling (optimizeDeps)
When Vite pre-bundles dependencies on startup or when it discovers new imports during development, the server can become unresponsive for several seconds. During this time the WebSocket connection may time out and drop, showing the “server connection lost” message.
You see this most often with large monorepos or projects that import many heavy dependencies.
Pre-bundle known dependencies upfront:
export default defineConfig({
optimizeDeps: {
include: [
'react',
'react-dom',
'lodash-es',
'axios',
'@mui/material',
// Add all heavy or frequently used deps
],
},
})By listing dependencies in optimizeDeps.include, Vite bundles them during the initial startup rather than discovering and processing them lazily. This prevents mid-session re-bundling that causes connection drops.
Exclude problematic dependencies:
If a specific dependency causes Vite to hang during pre-bundling, exclude it:
optimizeDeps: {
exclude: ['problematic-package'],
},Clear the Vite cache:
Corrupted or outdated dependency cache can cause repeated re-bundling cycles:
rm -rf node_modules/.vite
npm run devOr use the --force flag:
npx vite --forceThis rebuilds the entire dependency cache from scratch.
Increase the WebSocket timeout:
If pre-bundling is simply slow (large project, slow disk), you can increase the client-side timeout so the browser waits longer before declaring the connection lost:
server: {
hmr: {
timeout: 60000, // 60 seconds instead of default 30
},
},This is a band-aid, not a fix. Prefer reducing the pre-bundling time by using optimizeDeps.include.
If dependency resolution itself is failing rather than just being slow, see our guide on fixing “Cannot find module” resolution errors.
Still Not Working?
If none of the fixes above resolved the issue, check these less common causes:
Network drives and remote filesystems. Vite cannot watch files on NFS, SMB/CIFS, or SSHFS mounts reliably. Native file system events do not propagate across network boundaries. Move your project to a local disk or enable polling (server.watch.usePolling: true).
Custom middleware blocking the upgrade. If you use server.middlewareMode or add custom Express/Connect middleware before Vite, ensure it does not intercept or consume the WebSocket upgrade request. WebSocket upgrades are HTTP Upgrade requests and middleware that calls next() incorrectly or handles all requests can block them. Check that your middleware passes through requests with Upgrade: websocket headers.
Browser extensions interfering. Ad blockers, privacy extensions, and developer tools extensions can block WebSocket connections. Test in an incognito window with all extensions disabled. If HMR works in incognito, enable extensions one at a time to find the culprit.
Vite version compatibility. Some HMR bugs exist in specific Vite versions. Check the Vite changelog for known issues. Upgrading to the latest patch version often resolves obscure connection bugs:
npm install vite@latestPort conflicts. If another process is using the same port as Vite’s WebSocket, the connection fails. Check for port conflicts with your system:
# Linux/macOS
lsof -i :5173
# Windows
netstat -ano | findstr :5173If the port is occupied, either kill the other process or configure Vite to use a different port. See fixing port 3000 already in use for detailed steps on resolving port conflicts.
VPN software. Some VPN clients reroute all localhost traffic through the VPN tunnel, breaking WebSocket connections. If you recently connected to a VPN and HMR stopped working, try disconnecting the VPN or configuring split tunneling to exclude local addresses.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: React Warning: Failed prop type
How to fix the React 'Warning: Failed prop type' error. Covers wrong prop types, missing required props, children type issues, shape and oneOf PropTypes, migrating to TypeScript, default props, and third-party component mismatches.
Fix: Express Cannot GET /route (404 Not Found)
How to fix Express.js Cannot GET route 404 error caused by wrong route paths, missing middleware, route order issues, static files, and router mounting problems.
Fix: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
How to fix the JavaScript heap out of memory error by increasing Node.js memory limits, fixing memory leaks, and optimizing builds in webpack, Vite, and Docker.
Fix: TypeError: x is not a function
How to fix JavaScript TypeError is not a function caused by wrong variable types, missing imports, overwritten variables, incorrect method names, and callback issues.