Free Website Hosting for Students: Render, Vercel, Railway and GitHub Pages Compared

How do you put a project website online for free? Match the host to the project: static sites go on GitHub Pages or Cloudflare Pages (free, fast, no server), while a Node, Django or Laravel backend with a database goes on Render or Railway's free tiers with MongoDB Atlas or a free Postgres for data. This guide compares the platforms on real limits — sleep behavior, bandwidth, build minutes — then walks through two complete deploys: a static site and a Node API with a database.

Written by Projectech15 min readPublished
For B.E./B.Tech students deploying their final-year project website on free hosting tiers Topics: Render, Vercel, Railway, GitHub Pages, MongoDB Atlas
Illustration of free website hosting: a laptop with a rocket launching a browser window into a cloud, surrounded by coins and a crossed-out price tag.
Illustration generated for this guide.
In this guide

A project that only runs on your laptop is a private achievement; the same project on a public URL is a demonstrable one. Examiners open links, recruiters click portfolios, and "it's running at this URL" beats "it works on my machine" in every evaluation that matters. The good news: a student project can be hosted for genuinely zero rupees. The catch: every free tier has limits — sleep timeouts, bandwidth caps, build minutes — and choosing without reading them is how demos die the night before submission. This guide compares the platforms on their actual constraints, then walks through two complete deployments step by step.

Short answer: which host for which project

Project shape Put it on Why
Static site (HTML/CSS/JS, or a static export) GitHub Pages or Cloudflare Pages Free, fast CDN, custom domains, no server to sleep
Node/Express API + static frontend Render or Railway (backend) + static host (frontend) Free tiers run persistent Node processes with auto-deploy from GitHub
Django / Laravel full-stack app Render or Railway Needs a real server process; PHP/Python don't fit serverless-first free tiers well
Needs a database MongoDB Atlas M0 (512 MB free) or a free-tier Postgres Separate free database; never bundle the DB on the same free web dyno for anything you care about

If you remember one sentence: static goes on static hosts, servers go on Render/Railway, data goes on a free managed database — and each layer's limits are checked before you build on it.

The platforms, compared on real limits

Free-tier limits change — platforms revise them every year or two — so treat the figures below as the shape of each deal at the time of writing and re-check the pricing page before you commit your architecture to them:

Platform What it hosts Key free-tier limits (verify current) Sleeps? Custom domain + HTTPS
GitHub Pages Static sites only ~1 GB site, ~100 GB bandwidth/month, 10 builds/hour No (static CDN) Yes, HTTPS automatic
Cloudflare Pages Static sites + light functions Generous bandwidth, ~500 builds/month No (static CDN) Yes, HTTPS automatic
Netlify Static sites + serverless functions ~100 GB bandwidth, ~300 build minutes/month No for static Yes, HTTPS automatic
Vercel (Hobby) Frontend frameworks, serverless functions ~100 GB bandwidth; serverless execution limits Functions are serverless (cold starts) Yes, HTTPS automatic
Render (free) Web services, static sites, cron jobs ~750 service hours/month; spins down after ~15 min idle Yes — first request after idle takes ~30–60 s Yes, HTTPS automatic
Railway (trial) Services + databases Trial credit allowance (historically a few dollars/month); usage-metered Depends on plan activity Yes, HTTPS automatic

Three patterns to notice. Static hosts don't sleep — a static export on GitHub Pages answers instantly at 3 a.m. because there is no server to wake. Free server tiers sleep — Render's free web service spins down after ~15 minutes without traffic, so the first visitor waits through a cold start. And bandwidth/build caps are the quiet killers: a demo video embedded on your homepage can eat a monthly bandwidth allowance if the whole class opens it at once — host heavy assets thoughtfully.

Honesty rule for your report: write the platform, the tier, and its limits into your documentation ("hosted on Render free tier; service sleeps after 15 min idle; database on MongoDB Atlas M0, 512 MB"). An examiner who asks "what happens when 50 people open it together" should get an answer grounded in these numbers, not optimism.

Walkthrough A: static site on GitHub Pages

For a markdown blog engine with static-site export, a portfolio, or any frontend-only project, this is the path of least resistance — and it never sleeps.

  1. Push the site to a GitHub repository. The built files (the dist/ or docs/ output, not just sources) must be in the repo, or built by an Action.
  2. Enable Pages: repository Settings → Pages → deploy from a branch (e.g. gh-pages) or via GitHub Actions. The Actions route rebuilds the site on every push — set it once and forget it.
  3. Wait for the build, then open https://<username>.github.io/<repo>/. First deploy takes a few minutes.
  4. Custom domain (optional): add a CNAME file with your domain, point your domain's DNS at GitHub Pages, and tick "Enforce HTTPS". Student tip: a clean subdomain like project.yourname.dev looks far more professional in a report than a raw github.io URL.

In Actions-based setups, the workflow checks out the code, runs the build (npm run build or equivalent), and publishes the output directory. Secrets the build needs go in the repository's Actions secrets — never in the workflow file or the repo. Keep the workflow minimal: checkout, setup runtime, build, deploy. If the build works locally but fails in Actions, the cause is almost always a Node version mismatch — pin the version in the workflow to match your machine.

Walkthrough B: Node + Express API on Render, with a database

For a URL shortener with click analytics or a personal expense tracker — a real backend plus a database — follow this path. It is the most common student full-stack deploy, and every step below maps to a failure in the diagnosis table later.

1. Prepare the app for a platform. Three non-negotiable changes:

  • Bind to the platform's port. Hardcoding app.listen(3000) is the #1 deploy failure. Use the environment's port with a local fallback:
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => console.log('listening on ' + PORT));
    
  • Externalise all secrets. Database URI, JWT secret, API keys — everything comes from environment variables. Add .env to .gitignore and verify with git status that it is untracked. (The git and GitHub guide covers the version-control hygiene this depends on.)
  • Declare the start command. A package.json with "start": "node server.js" and a committed package-lock.json so the platform installs exactly your dependency tree.

2. Create the free database first. On MongoDB Atlas: create the free M0 cluster (512 MB), create a database user, and — this is the step everyone misses — set Network Access to allow connections from anywhere (0.0.0.0/0), because Render's outbound IPs are not fixed on the free tier. Copy the connection string; it goes into an environment variable, never into code.

3. Create the Render web service. New → Web Service → connect the GitHub repo. Set the build command (npm install) and start command (npm start). Add environment variables: MONGODB_URI, JWT_SECRET, NODE_ENV=production. Deploy.

4. Verify like a sceptic. curl the health endpoint, then the real ones:

curl https://your-api.onrender.com/api/health
curl -X POST https://your-api.onrender.com/api/auth/login   -H "Content-Type: application/json"   -d '{"email":"aarav@example.com","password":"correct-horse-battery"}'

Then open the frontend and click through the actual user journey — login, create, list, logout — against the deployed backend, not localhost. "It worked locally" is not a deploy verification.

5. Expect the cold start. After ~15 minutes idle, the first request wakes the service (~30–60 s). Design for it: a /health endpoint, a frontend loading state that says what is happening, and a demo habit of opening the URL a minute before you present. Some teams add automated keep-alive pings; note that several platforms discourage artificial traffic to dodge sleep — the durable fix is designing for cold starts, not fighting the tier's terms.

The database question, settled

Need Free option Notes
Document data (flexible schema) MongoDB Atlas M0 — 512 MB Allowlist 0.0.0.0/0 for PaaS backends; connection string in env vars
Relational data Free-tier Postgres (Neon/Supabase/Render) Small storage caps; fine for project scale; backups limited — export before demo day
Cache / sessions In-memory in the app Good enough at project scale; note the limit in your report
File uploads (images, PDFs) Object storage free tier or the platform's persistent disk Never store uploads on ephemeral container filesystems — they vanish on redeploy

Two warnings from experience: free database tiers cap concurrent connections (often single digits) — a backend that opens a new connection per request will exhaust them under class-demo load, so use a single shared connection pool. And back up before demo day: export the database the night before and keep the file. A free tier owes you nothing, and "the platform had an incident" is not a recovery plan.

Custom domains, DNS and HTTPS

A custom domain is cheap polish with real evaluation value. The mechanics, whichever platform:

  1. Buy (or reuse) the domain; student programs and cheap TLDs keep this near zero cost.
  2. For static hosts: add the domain in the platform dashboard, create the DNS CNAME (or A) records it shows you, wait for DNS propagation (minutes to hours), enable HTTPS — the platform provisions the certificate automatically.
  3. For Render/Railway: same flow from their custom-domain settings; the API typically lives on api.yourdomain.com while the frontend takes the apex or www.

Verify HTTPS actually serves (open the https:// URL directly) and that the API's CORS settings allow the frontend's new domain — changing domains without updating CORS is a classic post-launch 401/CORS outage.

The sleep problem, quantified

Cold starts deserve numbers, because "it's a bit slow sometimes" is not an engineering statement:

Situation Typical latency
Static site on GitHub Pages / Cloudflare Pages < 1 s, any time of day
Warm Render free service 0.2–1 s
Cold Render free service (after ~15 min idle) 30–60 s for the first request, then warm
Serverless function cold start (Vercel/Netlify) 1–5 s typically

Design consequences: health checks and loading states are not polish, they are the UX of free hosting. For the demo, warm the backend before the audience arrives. For the report, state the numbers — "p95 latency under warm conditions, cold-start behavior documented" is the kind of line that signals you measured instead of hoped.

One platform or split: where the frontend lives

Two deployment shapes, both legitimate:

  • Split (recommended for teams): frontend on a static host (GitHub Pages/Cloudflare Pages), backend API on Render/Railway. Each deploys independently from its own repo or monorepo folder; a frontend bug never takes the API down. The cost is CORS configuration and two URLs to manage — both one-time setup.
  • Unified: the backend serves the frontend's static files (Express static middleware, Django/WhiteNoise, Laravel's public/). One service, one URL, no CORS at all. Simpler to reason about and to demo — but every frontend typo redeploys the backend, and the free service still sleeps.

The deciding question is team workflow: if two members work the frontend while two work the API, split deploys stop you stepping on each other. Either way, the frontend must read the API's base URL from its own environment config (a build-time variable), never a hardcoded localhost:3000 — search the codebase for localhost before every deploy; it is the most common string in broken production frontends.

Django and Laravel on Render: the differences from Node

The walkthrough above used Node, but the principles transfer with stack-specific details:

  • Django: the start command runs a production server, not the dev server — gunicorn myproject.wsgi:application (add gunicorn to requirements). Static files need WhiteNoise or the platform's static handling; never serve them with runserver in production. Run migrations as a deploy step (python manage.py migrate), and set ALLOWED_HOSTS to the platform's domain — the debug 400 "DisallowedHost" page is the Django rite of passage.
  • Laravel: needs the right PHP version declared (the platform defaults may lag), composer install --no-dev at build time, php artisan migrate --force on deploy, and APP_KEY set in env vars. Storage links (php artisan storage:link) must survive deploys — verify uploaded files after a fresh deploy, not just after the first one.

Both need a persistent process (which is why they sit on Render/Railway rather than serverless free tiers) and both sleep on free plans — the cold-start design advice applies unchanged.

Logs and monitoring: the "it broke overnight" drill

Free tiers keep logs briefly and then they are gone — so build the habit of looking:

  1. Read the deploy logs first, the runtime logs second. Most failures are build failures (wrong Node version, missing env var at build time) wearing a runtime costume. The platform dashboard shows both; check in that order.
  2. Add a lightweight uptime check. A free external monitor pinging /health every few minutes tells you the service is down before your examiner does. (This is monitoring, not a sleep-dodging hack — keep the interval honest and modest.)
  3. Log the request, not the secret. When debugging auth failures in production, log that verification failed and why the category was — never log tokens, passwords, or full connection strings. Production logs have a way of being screenshared in reviews.
  4. Know the retention window. If the platform keeps 7 days of logs, your "what happened last month" investigation happens in your own notes, not the dashboard. For the project report, a one-page "incidents and fixes" log compiled from these notes reads as genuine operational experience — because it is.

The last 72 hours: a launch timeline

  • T-72h: freeze features. Only deploy fixes from here. Export the database; verify the backup restores somewhere.
  • T-48h: full end-to-end pass on the live URL from a fresh browser (no cached sessions): register, login, core flows, payments in test mode, logout. Fix, redeploy, re-verify.
  • T-24h: custom domain and HTTPS verified; cold-start behavior measured and noted; demo script warmed — open every URL in the presentation order once, so caches and services are warm.
  • T-2h: final database export; confirm the report's URLs, credentials for the demo account, and the "if the internet dies" fallback (a local run or recorded walkthrough — hope is not a plan, but a backup plan is).
  • T-0: warm the backend URL one minute before presenting. Breathe. Demo the working system.

Diagnosis table: the failures you will actually meet

Symptom Likely cause Fix
Application failed to respond / deploy crash-loops App hardcoded port 3000; platform assigns its own via PORT Bind process.env.PORT with a local fallback
Build succeeds, app crashes on boot Missing environment variable (DB URI, secret) Compare the platform's env dashboard against your local .env keys
Works locally, 500s in production NODE_ENV=production hiding dev-only behavior, or a missing dependency in dependencies vs devDependencies Move runtime deps correctly; reproduce with NODE_ENV=production locally
MongoDB connection timeout IP allowlist blocking the platform's outbound IPs Set Atlas Network Access to 0.0.0.0/0 (with a strong DB password)
MongoServerError: bad auth Special characters in the DB password breaking the URI URL-encode the password in the connection string
Frontend 404 on page refresh SPA routing: the static host serves only exact file paths Add the platform's SPA fallback rewrite (e.g. /* → /index.html, status 200)
CORS errors after adding a custom domain Backend CORS still allows only the old onrender.com origin Update allowed origins to the new frontend domain
First demo click hangs ~45 s Cold start after idle Warm the URL before presenting; add loading states
Uploaded files vanish after redeploy Stored on the container's ephemeral filesystem Move uploads to object storage or a persistent disk

When to leave the free tier

Free is the right start, not necessarily the right forever. Upgrade triggers, in order of likelihood:

  1. Sleep is hurting real users — the project has actual users beyond the demo, and cold starts cost them.
  2. You hit a hard cap — bandwidth, build minutes, database storage — two months running.
  3. You need reliability guarantees — backups with real retention, uptime SLAs, support.

Until one of these is true, the free tier is not a compromise — it is the correct budget for a student project. Spend the saved money (and the saved ops time) on the project itself.

Pre-launch checklist

  • App binds process.env.PORT; no hardcoded ports, no local-only assumptions.
  • .env untracked by git; all secrets in the platform's environment dashboard.
  • Database on a managed free tier (not the same container as the app); IP allowlist set; connection via env var.
  • Health endpoint live; full user journey verified with curl and in the browser against the deployed URL.
  • Cold-start behavior known and documented; demo warms the URL first.
  • Custom domain + HTTPS working; CORS allows the frontend's domain.
  • SPA fallback rewrite configured if the frontend uses client-side routing.
  • Database exported and the backup file kept somewhere safe before demo day.
  • Platform, tier and its limits written into the project report.

Putting it together

The deploy strategy in one paragraph: static frontends on GitHub Pages or Cloudflare Pages where they never sleep; the API on Render or Railway with process.env.PORT, secrets in env vars, and auto-deploy from GitHub; data on MongoDB Atlas M0 or a free Postgres with the IP allowlist opened; a custom domain with automatic HTTPS on top. Verify with curl against the live URL, design for cold starts instead of fighting them, and back up the database before demo day. A project at a public URL — with its hosting limits stated honestly in the report — is finished in a way that localhost never is. More software-build concepts live in the Computer / IT branch hub.

More project guides

More in Computer / IT

JWT Authentication for Students: Tokens, Signatures, Refresh Flows and a Node.js Implementation

How does JWT login actually work? When a user logs in, the server issues a signed token in three parts — header, payload, signature. The client sends it back as an Authorization: Bearer header, and the server verifies the signature instead of looking up a session. This guide decodes a real token by hand, walks through the full login and refresh flow, and builds a working Node.js implementation with bcrypt password hashing, token rotation, and storage rules that survive a viva.

Read guide

SQL vs NoSQL for Final-Year Projects: Which Database Should You Pick?

MySQL or MongoDB for your final-year project? SQL databases store data in related tables with enforced schemas, joins and transactions — the right default when your data is structured and money or records must stay consistent. NoSQL document stores trade the rigid schema for flexible, nested documents that ship faster when your data shape keeps changing. This guide compares them with a worked hospital-appointment example in both, a decision table, and rules matched to common project archetypes.

Read guide

Docker for Student Projects: Images, Containers and Compose from Zero

End ‘it works on my machine’ failures: learn what Docker images and containers actually are, write lean Dockerfiles that exploit layer caching, persist data with volumes, orchestrate app-plus-database with Compose, and package an evaluator-proof submission — with the debugging table for every error you will definitely meet.

Read guide