Socket.io Connection Issue

Fixing Socket.IO Connection Errors in Frappe: The Case of the Missing Realtime Updates

 · 7 min read

You've set up WhatsApp integration in Frappe CRM, configured all the webhooks, generated tokens, and filled in every ID. Everything looks perfect. But there's one problem: new messages don't appear unless you manually refresh the page. The real-time magic is broken.

If you're staring at browser console errors about failed Socket.IO connections, you're likely dealing with a binding issue where the realtime service isn't listening correctly on IPv4. This blog walks through the exact problem, how to identify it, and the one-line code fix that restores real-time notifications.

⚠️ The Symptom: WhatsApp messages (or any real-time updates) don't appear automatically. Users must refresh the page to see new messages. Browser console shows net::ERR_CONNECTION_REFUSED errors for Socket.IO connections.

Why This Matters for WhatsApp Integration

In the context of WhatsApp integration with Frappe CRM, this fix is critical. Without a working Socket.IO connection:

  • New WhatsApp messages don't appear in the UI until manual refresh
  • Users might miss urgent messages thinking nothing has arrived
  • The entire real-time value proposition of WhatsApp integration is lost

With this fix, messages should appear instantly as they arrive, and the WhatsApp interface updates automatically—exactly as it should.

📱 Real-Time Confirmation: After applying this fix, test by sending a WhatsApp message to your test number. The message should appear in the Frappe WhatsApp interface within seconds, with no refresh needed.

1. The Problem: Socket.IO Can't Connect

Frappe uses Socket.IO for real-time communication—pushing notifications, updates, and messages to the browser without needing a page refresh. When this connection fails, real-time features stop working.

1.1 What the Errors Look Like

Open your browser's Developer Tools (F12) and check the Console tab. If you see a wall of errors like this, you're affected:

Failed to load resource: net::ERR_CONNECTION_REFUSED :9019/socket.io/?EIO=4&transport=polling&t=b166zuis:1 Failed to load resource: net::ERR_CONNECTION_REFUSED :9000/socket.io/?EIO=4&transport=polling&t=b18iqnf8:1 Failed to load resource: net::ERR_CONNECTION_REFUSED :9019/socket.io/?EIO=4&transport=polling&t=b18oz9a2:1 Failed to load resource: net::ERR_CONNECTION_REFUSED :9000/socket.io/?EIO=4&transport=polling&t=b1bkvo78:1 Failed to load resource: net::ERR_CONNECTION_REFUSED :9019/socket.io/?EIO=4&transport=polling&t=b1ckz9iv:1 Failed to load resource: net::ERR_CONNECTION_REFUSED

The browser is trying to connect to ports 9000 and 9019, but both are refusing the connection. This explains why real-time updates aren't coming through.


2. Finding the Correct Socket.IO Port

Before fixing anything, we need to know which port Socket.IO should be using. This information is available when you start your bench instance.

2.1 Check the Bench Console

When you run bench start or restart your bench, look for a line like this in the console output:

10:48:57 socketio.1 | Realtime service listening on: ws://0.0.0.0:9019

This tells us the realtime service is configured to run on port 9019. In your case, the port might be different—note it down.

2.2 Check common_site_config.json

You can also verify the configured port in your common_site_config.json file:

{ "socketio_port": 9019, ... }

This is the port that should be accepting connections. The errors for port 9000 can be ignored if 9000 isn't your configured port—the browser may try it as a fallback.


3. The Root Cause: IPv4 Binding

The Socket.IO server was only binding to IPv6 by default in some Frappe versions, leaving IPv4 connections unable to reach it. This is a subtle issue because 0.0.0.0 in the log suggests it's listening on all interfaces, but the underlying implementation wasn't honoring that correctly.

The fix explicitly tells the server to listen on IPv4 when not using a Unix Domain Socket (UDS).


4. The Fix: One Line Change in index.js

4.1 Locate the File

Navigate to the Socket.IO server file in your Frappe app directory:

apps/frappe/realtime/index.js

4.2 Find the Server Listen Block

Look for the section where the server starts listening. The problematic code looks like this:


let uds = conf.socketio_uds;
let port = conf.socketio_port;
server.listen(uds || port, () =>
{ if (uds) { console.log(`Realtime service listening on UDS: ${uds}`);
} else { console.log(`Realtime service listening on: ws://0.0.0.0:${port}`);
} });

4.3 Apply the Fix

Modify the server.listen() line to explicitly bind to "0.0.0.0" for IPv4:


let uds = conf.socketio_uds;
let port = conf.socketio_port;
server.listen(uds || port, uds ? undefined : "0.0.0.0", () =>
{ if (uds) { console.log(`Realtime service listening on UDS: ${uds}`);
} else { console.log(`Realtime service listening on: ws://0.0.0.0:${port}`);
} });

The key addition is the second argument: uds ? undefined : "0.0.0.0". This ensures that when not using a UDS, the server binds explicitly to all IPv4 interfaces.


5. Applying and Testing the Fix

5.1 Restart Bench

After saving the file, restart your bench instance:

bench restart

5.2 Verify the Change

Watch the console output when the socketio service starts. You should still see the same listening message, but the underlying binding has changed.

5.3 Check Browser Console Again

Reload your Frappe page and check the browser console. The errors should now look different:

GET http://127.0.0.1:9000/socket.io/?EIO=4&transport=polling&t=drq9e2za net::ERR_CONNECTION_REFUSED GET http://127.0.0.1:9000/socket.io/?EIO=4&transport=polling&t=drwfmc8x net::ERR_CONNECTION_REFUSED

Notice that errors for port 9019 (or your configured port) are gone. The only remaining errors are for port 9000, which can be safely ignored. The browser may still try port 9000 as a fallback, but the realtime service on your correct port is now reachable.


6. Manual Verification Methods

6.1 Direct URL Test

You can directly test the Socket.IO endpoint in your browser. First, try the port that's failing (usually 9000):

http://127.0.0.1:9000/socket.io/?EIO=4&transport=polling

This will likely show "Not Found" or an error—expected behavior.

Now try your actual Socket.IO port (replace 9019 with your port):

http://127.0.0.1:9019/socket.io/?EIO=4&transport=polling

A successful response looks like this:

0{"sid":"irgP-ObtF376TdTZAAAS","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000,"maxPayload":1000000}

This JSON response confirms the Socket.IO server is alive and accepting connections on the correct port.


7. Troubleshooting If It Still Doesn't Work

Issue Check
Still seeing errors on your configured port Confirm the port number matches what's in common_site_config.json and the console output. Verify the file was saved correctly and bench was restarted.
Port 9000 errors are normal—but what if 9019 errors persist? Check firewall settings. Ensure nothing else is using the port. Try a different port by changing socketio_port in common_site_config.json.
Socket.IO starts but WhatsApp still doesn't update The issue may be in the WhatsApp integration itself. Check that webhooks are configured correctly and messages are reaching Frappe.
Changes revert after bench update The index.js file may be overwritten when Frappe is updated. Document this fix and reapply after updates, or consider contributing the fix upstream.

8. TL;DR – Quick Reference

# 1. Identify your Socket.IO port
Check bench console: "Realtime service listening on: ws://0.0.0.0:XXXX"
Or check common_site_config.json

# 2. Locate the file
apps/frappe/realtime/index.js

# 3. Find the server.listen block
Look for: server.listen(uds || port, () => { ... })

# 4. Apply the fix
Change to: server.listen(uds || port, uds ? undefined : "0.0.0.0", () => { ... })

# 5. Restart bench
bench restart

# 6. Verify
Browser console should show NO errors on your Socket.IO port
Direct URL test: http://127.0.0.1:9019/socket.io/?EIO=4&transport=polling
Should return JSON, not "Not Found"

Wrap-Up

One line of code. That's all it took to restore real-time functionality in Frappe. This fix transforms WhatsApp integration from a tool that requires constant manual refreshing into a true real-time communication channel. Messages appear instantly, users stay productive, and the system works the way it was designed to.

The Socket.IO binding issue is subtle and easy to miss—especially when the console logs claim the service is listening on 0.0.0.0. But now you know exactly what to look for and how to fix it.


No comments yet.

Add a comment
Ctrl+Enter to add comment