I'm currently working on deploying my Next.js app (version 13.4.19) to a shared hosting provider that uses cPanel and LiteSpeed. Unfortunately, I'm encountering an error during deployment, and I could use some assistance in resolving it.
The error message I'm seeing is:
Error: http.Server.listen() was called more than once which is not allowed.
at Server.customListen [as listen] (/usr/local/lsws/fcgi-bin/lsnode.js:68:15)
at ipcPort (/home/[..webbroot..]/node_modules/next/dist/server/lib/server-ipc.js:73:17)
at new Promise (<anonymous>)
at createIpcServer (/home/[..webbroot..]/node_modules/next/dist/server/lib/server-ipc.js:72:27)
at initialize (/home/[..webbroot..]/node_modules/next/dist/server/lib/router-server.js:79:82)
at async Server.<anonymous> (/home/[..webbroot..]/node_modules/next/dist/server/lib/start-server.js:192:36)
As I'm on cPanel hosting, I can't use next start to run the app. Instead, I need to utilize the standalone build that results in a server.js file, which can be set as the entrypoint script for cPanel.
After researching, I discovered that LiteSpeed has a bootstrap node script called /usr/local/lsws/fcgi-bin/lsnode.js. I suspect that this lsnode.js script and Next.js might not be fully compatible.
My Questions:
- Has anyone encountered a similar issue when deploying a Next.js app to cPanel + LiteSpeed?
- Do you have any insights or ideas on how to address this problem and make Next.js and LiteSpeed work together seamlessly in this setup?
Thank you in advance for your help!
I found this document from Phusion Passenger. https://www.phusionpassenger.com/library/indepth/nodejs/reverse_port_binding.html But I have not managed to get this solution to work in the Next.js library.
I embed the source code of the lsnode.js script below:
/*
* Copyright 2002-2018 Lite Speed Technologies Inc, All Rights Reserved.
* LITE SPEED PROPRIETARY/CONFIDENTIAL.
*/
var EventEmitter = require('events').EventEmitter;
var os = require('os');
var fs = require('fs');
var http = require('http');
var util = require('util');
var net = require('net');
var socketObject = { fd: 0 };
module.isApplicationLoader = true;
global.LsNode = new EventEmitter();
startApplication();
function startApplication() {
var appRoot = process.env.LSNODE_ROOT || process.cwd();
var startupFile = process.env.LSNODE_STARTUP_FILE || 'app.js';
LsNode.listenDone = false;
if (process.env.LSNODE_ROOT != undefined) {
try {
process.chdir(process.env.LSNODE_ROOT);
} catch (err) {
console.error("Error setting directory to: " +
process.env.LSNODE_ROOT + ": " + err);
}
}
if (!startupFile.startsWith('/')) {
startupFile = appRoot + '/' + startupFile;
}
process.title = 'lsnode:' + appRoot;
var consoleLog = process.env.LSNODE_CONSOLE_LOG || '/dev/null';
fs.closeSync(1);
try {
fs.openSync(consoleLog, "w+");
} catch(e) {
fs.openSync('/dev/null', "w+");
}
http.Server.prototype.realListen = http.Server.prototype.listen;
http.Server.prototype.listen = customListen;
http.Server.prototype.address = lsnode_address;
var app = require(startupFile);
if (!LsNode.listenDone) {
if (typeof app.listen === "function")
app.listen(3000);
}
}
function lsnode_address() {
return process.env.LSNODE_SOCKET;
}
function customListen(port) {
function onListenError(error) {
server.emit('error', error);
}
// The replacement for the listen call!
var server = this;
if (LsNode.listenDone) {
throw new Error("http.Server.listen() was called more than once " +
"which is not allowed.");
}
LsNode.listenDone = true;
var listeners = server.listeners('request');
var i;
server.removeAllListeners('request');
server.on('request', function(req) {
req.connection.__defineGetter__('remoteAddress', function() {
return '127.0.0.1';
});
req.connection.__defineGetter__('remotePort', function() {
return port;
});
req.connection.__defineGetter__('addressType', function() {
return 4;
});
});
for (i = 0; i < listeners.length; i++) {
server.on('request', listeners[i]);
}
var callback;
if (arguments.length > 1 && typeof(arguments[arguments.length - 1]) == 'function') {
callback = arguments[arguments.length - 1];
}
server.once('error', onListenError);
server.realListen(socketObject, function() {
server.removeListener('error', onListenError);
if (callback) {
server.once('listening', callback);
}
server.emit('listening');
});
return server;
}
I also ran into this issue with running an API that was using Fastify. By default, Fastify listens to
localhostwhile it appears that LightSpeed aims to use127.0.0.1I changed my code to resemble this and it works now as expected:
So in your instance I'm not sure what Next.js uses under the hood for it's SSR, but try changing the host. Another note is that Express 4 works out of the box as well, so if there is an option to change the http engine/library that might be an option as well.