I recently challenged myself on TikTok by making a post saying:
"I will build your website for free for a week."
It was a chaotic, fun, and educational week. But the biggest plot twist didn't come from the code, it came from the infrastructure.
I had a client with a "vibe coded" Next.js project (generated fully by AI tools). It had a lot of flaws, and I could only correct so much, but they just needed help with the hosting. I quickly agreed because hosting is my specialty and I thought this would be easy—at least until I heard the details. He had already paid for a year of budget shared hosting (cPanel) and insisted we use it.
This was honestly uncharted waters. I always thought it was not possible, and after I looked up "host Next.js on shared hosting," the internet—and every AI assistant I asked—gave me the same answer:
"Impossible. Next.js requires too much RAM. Shared hosting kills long-running processes. You need a VPS or Vercel."
As a DevOps engineer, I took that personally.
I dug deeper and found a forgotten hero in the cPanel stack: Phusion Passenger. It turns out, you can host Next.js on cheap shared hosting, and it works surprisingly well.
Below is exactly how I did it.
The Secret Sauce: Phusion Passenger
Standard shared hosting is great for PHP but hostile to Node.js apps that need to run permanently in the background. If you try to run:
npm start
…the server's process monitor will eventually kill it for using too much memory.
However, most modern cPanel hosts use CloudLinux with Phusion Passenger.
Passenger acts as a bridge. Instead of your app running 24/7 consuming RAM, Passenger "wakes up" your app when a request comes in and handles traffic - kind of like serverless.
The trick is simple:
Build locally. Run only the production build on the server.
The Guide: Next.js on cPanel
Prerequisites
- A Next.js project
- cPanel access with Setup Node.js App enabled
- Node.js installed locally
Step 1: Create a Custom Server
Since shared hosting can’t reliably run next start as a long-lived process, we need a custom Node.js server that Phusion Passenger can manage.
This doesn’t replace Next.js — it simply gives Passenger a single entry point it understands.
Create a file called server.js in your project root:
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const dev = false;
const hostname = 'localhost';
const port = process.env.PORT || 3000;
const app = next({
dev,
hostname,
port,
conf: {
compress: true,
poweredByHeader: false,
generateEtags: false
}
});
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer(async (req, res) => {
try {
const parsedUrl = parse(req.url, true);
await handle(req, res, parsedUrl);
} catch (err) {
console.error('Error handling request:', err);
res.statusCode = 500;
res.end('Internal server error');
}
}).listen(port, () => {
console.log(`> Ready on http://${hostname}:${port}`);
});
});
Step 2: Optimize Next.js for Production
Create or update next.config.mjs:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
poweredByHeader: false,
compress: true,
typescript: {
ignoreBuildErrors: true,
},
images: {
deviceSizes: [640, 750, 828, 1080, 1200],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
// unoptimized: true,
},
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
fs: false,
net: false,
tls: false,
};
}
return config;
},
};
export default nextConfig;
You can make any improvements you want to this for health checks or logging
Step 3: Build Locally (Very Important)
Do not build on the server.
npm run build
Shared hosting RAM limits will almost certainly crash the process.
Step 4: Package the App
- Delete
node_modules - Zip the entire project folder
- Make sure the zip includes:
.nextpublicserver.js- config files
Step 5: Upload & Extract
- Open File Manager
- Navigate to
/home/youruser/myapp - Delete default files
- Upload your zip
- Extract it into folder myapp
Step 6: Setup Node.js in cPanel
- Open Setup Node.js App
- Click Create Application
- Configure:
- Node Version: match local
- Mode: Production
- Application Root:
myapp - Application URL: leave empty for default or add a specific one
- Startup File:
server.js - Add any environment variables if needed
Step 7: Install Dependencies
Back in Setup Node.js App:
- Click Run NPM Install
If that fails:
npm install
Run it inside the virtual environment via cPanel Terminal.
Step 8: Go Live
- Restart the Node.js app
- Open
public_html - Confirm that htaccess is created like this, it should have any environment variables you added.
- Delete any default
index.html - Visit your domain
First load may take 10–15 seconds (cold start). After that, it's smooth.
Conclusion
Is this better than Vercel? No.
Is it production-grade for huge traffic? Also no.
But for:
- Freelancers
- Budget clients
- Legacy hosting situations
- Simple applications probably with few users
…it works - and it works well.
Sometimes, the "impossible" is just an undocumented feature and hopefully this documents it for the next person.

Top comments (0)