Skip to main content

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

ValueEffect
"*"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":

  1. Check the request Origin (browser DevTools → Network tab → the failing request → Headers).
  2. Check what server cors.origin is matching against.
  3. 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);