How to Deploy a Node.js App to Render (And What to Know About Port Blocking)
Introduction
You've built your Node.js backend. It works perfectly on your machine. Now what? Getting it live on the internet is the next step, and it's easier than most people think.
In this article I'll show you how to deploy a Node.js app to Render, how to handle environment variables, and what you need to know about port blocking so your app actually works in production. This is based on how I deployed my own Taskflow application, including getting Nodemailer working for email sending.
Render vs Railway
Before we start, here is a quick comparison of the two most popular free platforms for deploying Node.js apps.
Render gives you a free tier that keeps your app live, though it spins down after 15 minutes of inactivity on the free plan. It has a clean dashboard, automatic deployments from GitHub, and supports environment variables out of the box. It is what I use for Taskflow and it has been reliable.
Railway also has a free tier and is slightly faster to set up. It gives you a certain amount of free usage hours per month before charging. Both platforms support Node.js, PostgreSQL, and environment variables.
For this article we will use Render because it is what I have production experience with, but the concepts apply to Railway as well.
What You Need Before Starting
Make sure you have:
A Node.js app pushed to a GitHub repository
A Render account at render.com (free to sign up)
Your environment variables ready (from your
.envfile)
Preparing Your App for Deployment
Before deploying there are a few things your app needs.
The PORT Variable
On your local machine your app probably runs on a hardcoded port like 3000. On Render, the platform assigns the port dynamically through an environment variable called PORT. Your app needs to respect that.
Make sure your server listens like this:
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
The || 3000 fallback means it uses 3000 locally but picks up whatever port Render assigns in production. If you hardcode 3000 without the fallback your app will fail to start on Render.
The Start Script
Make sure your package.json has a start script:
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
}
Render uses the start script to run your app in production. Not dev, not nodemon, just start.
Environment Variables
Never push your .env file to GitHub. Make sure it is in your .gitignore:
node_modules
.env
We will add the environment variables directly on Render in the next steps.
Deploying to Render
Step 1: Create a New Web Service
Log in to your Render dashboard and click "New" then select "Web Service".
Step 2: Connect Your GitHub Repository
Render will ask you to connect your GitHub account. Once connected, search for your repository and select it.
Step 3: Configure the Service
Fill in the settings:
Name: give your app a name like
my-users-apiRegion: choose the one closest to your users
Branch:
mainormaster, whichever you useBuild Command:
npm installStart Command:
npm startInstance Type: select Free
Step 4: Add Environment Variables
Scroll down to the Environment section before clicking Deploy. This is where you add everything from your .env file.
Click "Add Environment Variable" for each one:
DB_USER=your_postgres_username
DB_HOST=your_db_host
DB_NAME=your_db_name
DB_PASSWORD=your_db_password
DB_PORT=5432
JWT_SECRET=your_secret_key
JWT_EXPIRES_IN=24h
Never skip this step. If your app tries to start without these values it will crash immediately.
Step 5: Deploy
Click "Deploy Web Service". Render will pull your code from GitHub, run npm install, and start your app. You will see the build logs in real time.
Once the deployment is complete Render gives you a live URL that looks like https://your-app-name.onrender.com.
The Port Blocking Issue
This is something that catches a lot of developers off guard when deploying for the first time.
Cloud platforms block certain network ports to prevent spam and abuse. The most commonly blocked port is port 25, which is the old standard SMTP port for sending emails. If your app tries to send emails through port 25 in production it will silently fail.
The ports that work on both Render and Railway are:
Port 587 with STARTTLS, which is the recommended port for most email services
Port 465 with SSL, which is used by Gmail SMTP and some other providers
In my Taskflow app I use Nodemailer to send emails and it works perfectly on Render because I am using port 587. Here is how the Nodemailer transporter is configured:
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
})
secure: false tells Nodemailer to use STARTTLS on port 587 instead of SSL. If you set secure: true you need to use port 465 instead. Both work on Render. Just never use port 25.
If you are using Gmail, make sure you use an App Password instead of your actual Gmail password. Go to your Google Account, enable two-factor authentication, then generate an App Password specifically for your app. Store it as an environment variable and never commit it to your repository.
Automatic Deployments
One of the best things about Render is that once you connect your GitHub repository, every time you push to your main branch Render automatically redeploys your app. No manual steps needed.
This means your workflow becomes:
git add .
git commit -m "fix: update user validation"
git push origin main
And your live app updates within a couple of minutes.
Troubleshooting Common Issues
App crashes on startup: almost always means a missing environment variable. Check your Render logs and look for something like Cannot read properties of undefined. Go back to the environment variables section and make sure everything from your .env file is there.
502 Bad Gateway: usually means your app is not listening on process.env.PORT. Double check your server setup and make sure you are not hardcoding the port.
App spins down on free tier: Render's free tier spins down your app after 15 minutes of inactivity. The first request after it spins down takes 30 to 60 seconds while it wakes up. This is normal on the free plan. Upgrade to a paid plan if you need it always on.
Emails not sending: check which port you are using. Switch to 587 with secure: false or 465 with secure: true. Make sure your email credentials are set as environment variables on Render and that you are using an App Password if you are on Gmail.
Render vs Railway: Quick Comparison
| Render | Railway | |
|---|---|---|
| Free tier | Yes, spins down after inactivity | Yes, limited monthly hours |
| Auto deploy from GitHub | Yes | Yes |
| PostgreSQL support | Yes | Yes |
| Port 25 blocked | Yes | Yes |
| Port 587 and 465 | Open | Open |
| Dashboard | Clean, beginner friendly | More technical |
Both are great options. I use Render for Taskflow and it has been solid. If you want something slightly more developer focused with a faster setup, try Railway.
Conclusion
Deploying a Node.js app to Render is straightforward once you know the three things that matter most: use process.env.PORT for your server, add all your environment variables on the platform, and use port 587 or 465 for email sending instead of port 25.
Getting your app live is one of the most satisfying moments in building something. Once it's deployed, share the link, get feedback, and keep building.
Found this helpful? Follow me on Hashnode, connect with me on LinkedIn, and follow me on X for more articles on full stack development, Node.js, and backend architecture.




