CORS
The signaling server runs on a different origin from your web app — signaling.yourapp.com vs app.yourapp.com, or localhost:3001 vs localhost:5173 in dev. Browsers refuse cross-origin requests by default; you have to opt in.
The option
Server constructor accepts socket.io's cors option:
const server = new Server({
cors: {
origin: "https://yourapp.com",
credentials: true,
},
});
origin is what gets compared against the Origin request header.
Allowed values
| Value | Effect |
|---|---|
"*" | Any origin. Don't use in production unless intentional. |
"https://yourapp.com" | Single exact match |
["https://yourapp.com", "https://staging.yourapp.com"] | Array of exact matches |
(origin, cb) => cb(null, origin === ...) | Function — full control |
/^https:\/\/.*\.yourapp\.com$/ | Regex |
Production setup
Lock to your real origins:
const server = new Server({
cors: {
origin: process.env.ALLOWED_ORIGINS!.split(","),
credentials: true,
},
});
# .env or platform config
ALLOWED_ORIGINS=https://yourapp.com,https://www.yourapp.com,https://staging.yourapp.com
Development setup
origin: "*" is fine for npm run dev. Don't ship that to prod — any origin (including malicious sites embedded in iframes) can connect to your server and consume capacity.
Credentials
If you use cookie-based auth on the signaling endpoint, set credentials: true on the server and withCredentials: true on the client:
// server
new Server({ cors: { origin: "https://yourapp.com", credentials: true } });
// client
io(URL, { withCredentials: true, iceServers: [...] });
Note: credentials: true is incompatible with origin: "*" per the CORS spec. The browser refuses to send cookies cross-origin to a wildcard. You must list exact origins.
Wildcard subdomains
socket.io accepts a regex:
cors: { origin: /^https:\/\/.*\.yourapp\.com$/ }
Or a function:
cors: {
origin: (origin, cb) => {
if (!origin) return cb(null, true); // allow same-origin / no-origin requests
if (origin.endsWith(".yourapp.com")) return cb(null, true);
cb(new Error("origin not allowed"));
},
}
The function form is the most flexible — you can check against a database, environment variable, etc.
Headers and methods
socket.io sets the necessary CORS headers automatically once you specify origin. You don't need to configure methods or allowedHeaders unless you've added custom headers.
Behind a reverse proxy
If your nginx is doing TLS termination (see Deployment), make sure it forwards the Origin header to the Node process. nginx does by default; some proxies strip it.
location /socket.io/ {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
# Origin is forwarded automatically — don't strip it.
}
If you set CORS on the proxy and on socket.io, you can end up with double-Access-Control-Allow-Origin headers and the browser refuses the connection. Pick one — let socket.io do it (recommended) or let the proxy do it.
Debugging CORS errors
If the browser console says "blocked by CORS":
- Check the request
Origin(browser DevTools → Network tab → the failing request → Headers). - Check what server
cors.originis matching against. - If
origin: "*", no CORS error happens — so if you see one with"*"set, the server isn't actually receiving that config (build cache, env var not loaded, wrong file).
The error always points at the server config, not the client. Clients can't "fix" CORS — they can only request a different origin.
Same-origin
If your web app and signaling server share an origin (https://yourapp.com for both), CORS doesn't apply. You don't need any cors.origin setting.
This is rare in practice — usually the signaling server lives on a subdomain or different port. But if you co-host them (e.g. Express app serving both static files and the socket.io server), it's the simplest setup.
import express from "express";
import http from "node:http";
import { Server } from "rtc.io-server";
const app = express();
app.use(express.static("public"));
const httpServer = http.createServer(app);
const io = new Server(httpServer); // no cors needed; same origin
httpServer.listen(3001);