Problem Summary
I have a Node.js monorepo with both an EJS-based admin panel and a React frontend. The app works perfectly on localhost, but when deployed to cPanel with Passenger, only the React app works—the admin panel routes return 404 or don't load.
Environment
Server: cPanel with CloudLinux Passenger
Node.js Version: 22
Stack: Express.js, EJS (admin), React (frontend)
Deployment: Passenger with
PassengerBaseURI "/lwbl"
Current Configuration
.htaccess (auto-generated by cPanel)
# DO NOT REMOVE. CLOUDLINUX PASSENGER CONFIGURATION BEGIN
PassengerAppRoot "/home/devdusjq/public_html/lwbl"
PassengerBaseURI "/lwbl"
PassengerNodejs "/home/devdusjq/nodevenv/public_html/lwbl/22/bin/node"
PassengerAppType node
PassengerStartupFile index.js
# DO NOT REMOVE. CLOUDLINUX PASSENGER CONFIGURATION END
Express Server (index.js)
const express = require("express");
const app = express();
const path = require("path");
const cookieParser = require("cookie-parser");
const authValidator = require("./middleware/authValidator");
require("dotenv").config();
require("./db");
// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cookieParser());
// Serve uploads folder
app.use("/uploads", express.static(path.join(__dirname, "uploads")));
// Serve admin assets (CSS, JS, images)
app.use(
"/admin/assets",
express.static(path.join(__dirname, "views/admin/assets"))
);
// Set view engine for EJS
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views/admin"));
// API routes for React (NO AUTH on /api)
app.use("/api", require("./controller/apiHandler"));
// Apply validator ONLY to admin routes
app.use("/admin", authValidator());
// Admin EJS routes - BEFORE React fallback
app.use("/admin", require("./ejs_routes/admin"));
app.use("/admin/categories", require("./ejs_routes/categories"));
app.use("/admin/products", require("./ejs_routes/products"));
app.use("/admin/orders", require("./ejs_routes/orders"));
app.use("/admin/permissions", require("./ejs_routes/permission"));
app.use("/admin/roles", require("./ejs_routes/roles"));
app.use("/admin/subscriptions", require("./ejs_routes/subscriptions"));
app.use("/admin/moderation-logs", require("./ejs_routes/moderationLog"));
// REACT APP - Serve static files at /lwbl
const reactAppPath = path.join(__dirname, "build");
app.use("/lwbl", express.static(reactAppPath));
app.get("/lwbl/*", (req, res) => {
res.sendFile(path.join(reactAppPath, "index.html"));
});
// Root redirect
app.get("/", (req, res) => {
res.redirect("/lwbl");
});
// Start server
const PORT = process.env.PORT || 6871;
app.listen(PORT, () => {
console.log(`App is live on port: ${PORT}`);
console.log(`React app: http://localhost:${PORT}/lwbl`);
console.log(`Admin panel: http://localhost:${PORT}/admin`);
});
Expected vs Actual Behavior
Expected (Localhost - Working)
http://localhost:6871/lwbl→ React app loadshttp://localhost:6871/admin→ Admin panel loads
Actual (cPanel Production - Not Working)
https://mydomaincom/lwbl→ React app loadshttps://mydomaincom/lwbl/admin→ 404 or blank page
What I've Tried
Verified all files (
ejs_routes/*,views/admin/*) are uploaded to cPanelChecked
stderr.log- no errors shownConfirmed file permissions are correct (755 for directories)
Verified
authValidatormiddleware isn't blocking requests (works on localhost)
Questions
Does
PassengerBaseURI "/lwbl"affect how Express routes are resolved?Should admin routes be
/lwbl/admininstead of just/adminwhen using Passenger?Is there a routing conflict between the React catch-all (
/lwbl/*) and admin routes?
Any guidance on resolving admin panel routing with Passenger would be greatly appreciated!