Netlify Backend Hosting: What Functions Can and Cannot Do
Sagar Tayde · · 13 min read
A Netlify backend is usually a set of serverless functions that sit next to your frontend and answer API calls. That works well for small jobs, and it runs into hard limits once your backend needs a long-running process, a queue worker, WebSockets or a database close to Indian users. This guide is for developers whose frontend already lives on Netlify and who want to know what the backend can run there, what it cannot, and where to put the rest.
All Netlify limits below are from Netlify’s own documentation as of November 2026, mainly its functions configuration reference. Netlify changes these numbers over time, so check their docs before you design around one.
Can you host a backend on Netlify?
Yes, with conditions. Netlify runs backend code as functions: short-lived handlers that start for a request and stop after it. A normal request can run for 60 seconds, a background task for 15 minutes, and a scheduled task for 30 seconds. A long-running Node.js server is only available through Netlify Server, which is in Early Access.
So the honest answer depends on what “backend” means for your app. A contact form, a payment webhook or a small JSON API fits well. A chat server, a queue consumer, a job that takes 40 minutes, or an API that keeps a pool of database connections open does not fit functions, and needs either Netlify Server or a host that runs ordinary processes.
The five places backend code can run on Netlify
Netlify has five different runtimes, and each has its own rules. Mixing them up is the most common reason a backend works on a laptop and fails after deploy.
| Runtime | How long it can run | Good for |
|---|---|---|
| Synchronous function | 60 seconds per request | API routes, form handlers, webhooks |
| Background function | 15 minutes, client gets an empty 202 |
Slow follow-up work after a request |
| Scheduled function | 30 seconds, on a cron schedule in UTC | Small periodic jobs |
| Edge function | 50 ms of CPU time per request | Redirects, headers, personalisation |
| Netlify Server (Early Access) | Long-lived process; a request up to 30 minutes (2 hours on Pro) | Express, Fastify, WebSockets |
Synchronous functions
This is the default. A function gets a Request and returns a Response. Netlify lists the
synchronous execution limit as 60 seconds and marks it as not configurable. A buffered request or
response can be up to 6 MB, and binary uploads are Base64-encoded, which brings the real upload limit
closer to 4.5 MB. Functions get 1 GB of memory by default; raising it (up to 4 GB) is a Pro and
Enterprise setting.
Older answers on Netlify’s forum give lower limits. The limit has moved over time, so trust the current docs, not an old thread.
Background functions
Add background: true and the function can run for up to 15 minutes. The catch: the caller gets an
empty 202 straight away, so the result has to go somewhere else, such as a database row the
frontend polls. If the function throws, Netlify retries it after one minute and again two minutes
later, so the work must be safe to run twice. The request payload for a background function is capped
at 256 KB.
Scheduled functions
A scheduled function runs on a cron expression, has a 30-second limit, and runs in UTC. It cannot
receive a payload, cannot be called by URL, and only fires for published deploys, not previews. For
Indian teams the UTC part matters: 0 9 * * * is 2:30 PM IST, not 9 AM.
Edge functions
Edge functions run close to the visitor but get 50 ms of CPU time per request. They are built for rewriting a request or a response, not for running your API.
Netlify Server
Netlify Server is the newest option and the one that changes the answer most. It runs a normal Node.js server (Express, Hono or Fastify) without a wrapper, keeps state in memory between requests, and holds WebSocket connections open. The limits are worth reading before you plan around it:
- It is in Early Access. Netlify says it is “meant for evaluation, experimentation, and low-volume production use”, and that its APIs and behaviour might change.
- It always scales to zero: an instance stops after 5 minutes without requests. There is no always-on setting yet.
- It runs up to 3 instances, or 1 on the Free and Personal plans.
- An instance is replaced after 8 hours even when busy, and an outbound connection, such as one to your database, is closed after 60 seconds with no traffic.
- It runs in two regions only: US East (Ohio) and EU (Frankfurt).
- Full-stack frameworks such as Next.js and Astro do not run on it yet.
As of November 2026 it is free during Early Access, and Netlify says pricing in credits will be announced before general availability.
Running Express on Netlify Functions
Netlify’s own Express guide uses the serverless-http package to turn the whole app into one
function:
// netlify/functions/api.js
import express, { Router } from "express";
import serverless from "serverless-http";
const api = express();
const router = Router();
router.get("/hello", (req, res) => res.send("Hello World!"));
api.use("/api/", router);
export const handler = serverless(api);
# netlify.toml
[functions]
external_node_modules = ["express"]
node_bundler = "esbuild"
[[redirects]]
force = true
from = "/api/*"
status = 200
to = "/.netlify/functions/api/:splat"
Netlify’s guide is clear that every function limit still applies, and that Express should not be deployed as a background or scheduled function.
What breaks when an Express app becomes a function
Your Express code does not change, but the way it runs does. These are the problems people hit most.
Memory does not survive between requests
A function instance can be reused or thrown away at any time. An in-memory session store, a rate-limit counter in a JavaScript object, or a cache in a global variable will look fine in testing and then lose data in production. Keep state in a database, a key value store or a cookie.
WebSockets have nowhere to live
A function answers one request and ends, so it cannot hold a socket open. Socket.IO and the ws module
need a process that stays up. On Netlify, that means Netlify Server, or a separate managed WebSocket
service with a function that only issues tokens, which is the approach Netlify’s own blog describes.
Long jobs hit the wall
Generating a large report, resizing a batch of images or calling a slow AI API can pass 60 seconds. Moving it to a background function buys 15 minutes, but you lose the response. Anything longer, or anything that should keep working through a queue all day, needs a worker process.
Database connections pile up
PostgreSQL gives every connection its own process and memory. When many function instances each open their own connection, the database runs out of slots long before it runs out of capacity. Use a connection pooler in front of the database if your backend stays on functions.
The first request is slow
A function that has not run recently has to start before it can answer. A reply on Netlify’s forum to a user asking for an India region in 2023 said cold starts, not distance, were the cause of the slowness that user saw. On a backend built from many small functions, this adds up.
Databases for a Netlify backend
Netlify now has three answers for data:
- Netlify Database: managed Postgres, built in. Netlify’s launch post says its partnership with Neon powers the new databases. It is on credit-based plans only. The Free plan allows 1 compute unit per database, 48 compute units per billing period and 5 GB of storage, and the database sleeps after 5 minutes of no queries. Netlify says waking it typically adds “well under a second, but this is not guaranteed”. Only Pro and Enterprise can keep it always on. Netlify’s docs do not say which region it runs in.
- Netlify Blobs: a key/value store for files and unstructured data. Netlify describes it as optimised for frequent reads and infrequent writes, with no concurrency control: the last write to a key wins. It is not a relational database.
- An external database: any Postgres you can reach over TLS.
Whichever you choose, put the database in the same region as the code that queries it. A single API request often runs several queries one after another, and every one of them pays the round trip.
Where Netlify runs your backend, seen from India
Netlify’s CDN serves static files from many locations, but backend code runs in one region you pick.
- Functions default to US East (Ohio). Changing the region is a Pro and Enterprise feature.
- The self-serve function regions in Asia are Singapore, Tokyo and Sydney. There is no India region in the list.
- Netlify Server runs in Ohio or Frankfurt only.
For an app with users in India, that means every API call travels to another country and back. If the function also talks to a database in a third place, each query adds another long round trip. Moving the function to Singapore helps; it does not remove the distance.
The common split: frontend on Netlify, backend elsewhere
You can keep what Netlify does best, which is building and serving the frontend, and run the API, database and workers on a platform built for long-running processes. The frontend calls the API over HTTPS.

Two small changes make the split work. First, the frontend reads the API address from an environment variable set in Netlify’s site settings, so you never hard-code it:
// frontend, built with Vite
const API_URL = import.meta.env.VITE_API_URL; // e.g. https://api.example.in
const res = await fetch(`${API_URL}/api/orders`, { credentials: "include" });
Second, the API allows the frontend’s exact origin, including https://:
// server.js on the backend
import cors from "cors";
app.use(cors({
origin: ["https://example.in", "https://your-site.netlify.app"],
credentials: true,
}));
If you are weighing a full move instead of a split, the Netlify alternatives for Indian teams post compares the options.
If you use cookies across two domains, put the API on a subdomain of your main domain (for example
api.example.in), so the browser treats the cookie as same-site.
Running the backend, Postgres and workers in Mumbai
This is where Voroa fits. Each Netlify runtime has a direct equivalent that runs as a normal process in Mumbai:
| On Netlify | On Voroa | What changes |
|---|---|---|
Synchronous function or Express via serverless-http |
A web service | One long-running server that reads PORT; no per-request wrapper |
| Netlify Server WebSockets | WebSockets on the same web service | No fixed maximum connection length; send a ping every 30 seconds |
| Background function | A background worker | A process that stays running, so a job is not cut off at 15 minutes |
| Scheduled function | A scheduled task or a cron job | Schedules run in IST, not UTC |
| Netlify Database | Managed PostgreSQL | Runs in Mumbai, next to the API |
Turn the function back into a server
Split the Express app from the way it is started. Then the same app can run as a server on Voroa and,
while you migrate, still as a Netlify function:
// app.js - your routes, unchanged
import express from "express";
export const app = express();
app.use(express.json());
app.get("/api/health", (req, res) => res.json({ ok: true }));
// server.js - the start command on Voroa: node server.js
import { app } from "./app.js";
const port = process.env.PORT || 3000;
app.listen(port, "0.0.0.0", () => console.log(`listening on ${port}`));
Voroa sets PORT for you, so reading it is all the server needs. The steps for a first deploy are in
the Node.js deploy guide, and the Node.js documentation
covers the runtime itself. Express’s own
production best practices apply as
they would on any server, such as setting NODE_ENV=production.
Add WebSockets to the same service
A Voroa web service accepts WebSocket connections on the same port as HTTP. With the ws module:
import { createServer } from "http";
import { WebSocketServer } from "ws";
import { app } from "./app.js";
const server = createServer(app);
const wss = new WebSocketServer({ server, path: "/ws" });
wss.on("connection", (ws) => {
ws.on("message", (msg) => ws.send(`echo: ${msg}`));
});
server.listen(process.env.PORT || 3000, "0.0.0.0");
A connection with no traffic for about 100 seconds can be closed on its way through the edge, so send a ping every 30 seconds and reconnect after a deploy. The WebSockets guide has the full client side.
Replace background functions with a worker
A worker has no web address. It runs your start command and keeps running, which suits a job queue. Here is a minimal one that uses a Postgres table as the queue, so it needs nothing beyond the database:
// worker.js - start command: node worker.js
import pg from "pg";
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
async function next() {
const { rows } = await pool.query(`
UPDATE jobs SET status = 'running'
WHERE id = (
SELECT id FROM jobs WHERE status = 'queued'
ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1
)
RETURNING *`);
return rows[0];
}
for (;;) {
const job = await next();
if (!job) { await new Promise((r) => setTimeout(r, 2000)); continue; }
await handle(job); // your slow work, no 15-minute cut-off
await pool.query("UPDATE jobs SET status = 'done' WHERE id = $1", [job.id]);
}
The API inserts a row and answers at once; the worker picks it up. SKIP LOCKED lets you run more than
one worker without two of them taking the same job. Voroa’s
background workers cost the same as a web service of the same size.
If a job is meant to run and finish on a timetable instead, use a scheduled task or a cron job, not a worker. A worker that exits gets started again. The guide on choosing between a worker and a cron job explains the difference.
Put Postgres next to the API
Create a managed PostgreSQL database in India and set its URL as
DATABASE_URL on the web service and the worker. Because the API and the database are both in Mumbai,
a request that runs five queries does not pay five trips abroad. If part of your backend stays on
Netlify Functions for a while, connect those functions through the pooled port when your database
lists one; the connection pooling guide explains which endpoint
to use when.
What a Mumbai backend costs
You can try the whole setup at Rs 0. The free Nano size has 512 MB of memory and 0.1 vCPU, and a free web service sleeps after 15 minutes without requests. The free tier also includes one managed Postgres with 1 GB of storage, subject to free capacity.
For production, paid web sizes start at Rs 298 a month plus GST for 1 GB and 1 vCPU, always on. Adding the DB Starter Postgres (Rs 299) brings an API with its database to Rs 597 per month before GST. You pay in rupees by UPI, Indian card or netbanking, and get a GST invoice. See Voroa pricing for every size.
Run your Netlify backend on Voroa
Keep the frontend on Netlify if you like it there. Move the API, the database and the workers to Voroa, where they run as normal processes in Mumbai, deploy from GitHub on every push when auto-deploy is on, and are billed in INR. See what a Voroa web service includes, or read the wider backend API hosting guide first.
FAQ
Is Netlify good for backend development?
It is good for small, request-shaped backend work: webhooks, form handlers, and short API routes. It is a weaker fit for long-running servers, queues and WebSockets, unless you use Netlify Server, which is still in Early Access.
Can I run Socket.IO on Netlify?
Not on ordinary Netlify Functions, because a function cannot keep a connection open. Netlify Server
supports WebSockets, but an Express app exported as it is answers WebSocket requests with a 501; wrap
it in http.createServer() and export the server, or start it with app.listen(process.env.PORT). Any host
that runs a long-lived Node.js process also works.
Does Netlify have a server in India?
Netlify’s documented function regions do not include India; the closest are Singapore and Tokyo. Netlify Server lists only Ohio and Frankfurt.
Can I use Netlify for the frontend and another host for the API?
Yes. Set the API URL as a build-time environment variable on Netlify, and allow your Netlify domain in the API’s CORS settings.
Do I need to rewrite my Express app to leave Netlify Functions?
No. Remove the serverless-http wrapper, call app.listen(process.env.PORT), and your routes stay the
same.