You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署Next.js到cPanel+LiteSpeed遇http.Server.listen()重复调用错误求助

Next.js 13.4.19 部署至 cPanel+LiteSpeed 共享主机报错问题

问题背景

将版本为13.4.19的Next.js应用部署到cPanel+LiteSpeed共享主机时出现错误。由于cPanel环境限制,无法通过next start启动应用,需使用独立构建生成的server.js作为入口脚本。经排查,怀疑LiteSpeed自带的/usr/local/lsws/fcgi-bin/lsnode.js启动脚本与Next.js存在兼容性冲突。

报错信息

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)

核心问题

  1. 是否有用户在同类环境部署Next.js时遇到过相同问题?
  2. 有哪些可行方案解决此兼容性问题,实现Next.js与LiteSpeed的协同运行?

调研进展

曾查阅Phusion Passenger关于反向端口绑定的相关文档,但未成功适配到Next.js场景中。

lsnode.js脚本源代码

/*
 * 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;
}

问题分析与解决方案

问题根源

从报错栈和lsnode.js源码可以看出:LiteSpeed的customListen方法会拦截http.Server.listen调用,设置LsNode.listenDone = true后禁止后续再次调用。但Next.js启动时,除主服务器外还会创建IPC服务器(对应server-ipc.js逻辑),导致第二次调用listen触发错误。

可行解决方案

  • 禁用Next.js的IPC服务器:在server.js入口添加配置,通过环境变量或修改构建后的代码跳过IPC服务器初始化。例如启动时传入--disable-ipc参数(若Next.js支持),或手动注释createIpcServer相关逻辑。
  • 绕过LiteSpeed的listen拦截:在server.js最顶部保存原始http.Server.listen方法,恢复后再启动Next.js,待所有listen调用完成后按需重新应用拦截:
    // server.js顶部添加
    const originalListen = require('http').Server.prototype.listen;
    require('http').Server.prototype.listen = originalListen;
    
    // 导入并启动Next.js
    const { createServer } = require('next');
    const app = createServer({ /* 你的应用配置 */ });
    
  • 切换静态导出模式:若应用无需SSR、API路由等服务端功能,使用next export生成纯静态文件,直接部署到cPanel静态目录,彻底绕过Node.js运行时问题。
  • 联系主机服务商更新环境:当前lsnode.js版本标注为2018年,可能存在兼容性滞后。可联系服务商询问是否有更新版本,或是否支持用Passenger替代lsnode作为Node.js运行时。
  • 自定义适配脚本:编写中间启动脚本,先处理LiteSpeed的listen拦截逻辑,再启动Next.js,手动管理LsNode.listenDone状态以兼容多次listen调用。

内容的提问来源于stack exchange,提问作者Ivar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 01:02:13