Recently, after enjoying doing lots programming, I soon found myself wanting to branch out into the wider world - the internet! However, on a tight budget, I soon found this very difficult. After lots of research, I found it very hard looking for ways to set up a website on my own that didn't involve sketchy free trials. I also knew that for the website I wanted to host, I'd need a server backend, but very few services actually provide this without it becoming serverless. I knew would have to self-host on a small computer located at home.
This guide aims to help guide you step-by-step to making a dynamic website at home! We'll cover:
- Creating the website server and pages
- Going public
- Giving your website a good name (and URL)
- Securing with https
Note that you will be coding, as it allows for much more flexibility than the no-code versions. If you do want a no-code website, you should use an online service. Google Sites is a good, free, solution.
- A brain π§
- Basic knowledge on HTML/CSS/Javascript (I'll explain what all the code means. It'll be useful when actually making your website content though!)
- A network connection (you will need admin access to the router)
- A computer that can connect to said network (You will also need admin access. Plus, this should ideally able to stay at home)'
- An email address
- Node.JS and NPM installed (These should come together with most installations - https://nodejs.org/en)
- An IDE/Text Editor installed (Any will do - Even windows notepad is fine!)
βTop tip: Get lost in any of the code along the way? You can check it against the Code Dump using the folders above.
Before we get you onto the internet, we will start by making the actual website. Please note that you can edit this later! In this example, we will be creating a website that counts how many visits it has had. We will save this to a file, and display it when someone visits it!
Begin by creating a folder for your website somewhere on your computer (E.G The Documents folder). Inside that, create the following files (they can just be empty for now):
Your folder/
βββ public/
β βββ index.html
β βββ styles.css
β βββ scripts.js
βββ server.js
Our public folder will host all of the files that we need to serve - This will show all the nice pretty stuff! The server.js file will be what then sends it to the network!
In this guide, I'll indicate what file everything goes in, and what it does. If you're interested in how it works, read the comments in the code snippets (They begin with a // in javascript, are surrounded by /* */ in CSS, or <!-- --> in HTML)!
Lets Begin coding!
<!-- Define the document type -->
<!DOCTYPE html>
<!-- Set the language attribute -->
<html lang="en">
<!-- Header stuff, like including the styles.css file -->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- The title is what is displayed in the header bar in the browser -->
<title>The Counter thing!</title>
<!-- The link tag allows us to link the CSS File which makes everything look nice :) -->
<link rel="stylesheet" href="./styles.css">
<!-- Lets import the JavaScript file.
The SRC attribute allows us to pick it from a file
The ./ means "Go to the folder this file is currently in".
-->
<script src="./scripts.js"></script>
</head>
<!-- The main page content. The class attribute helps with CSS later -->
<body class="background">
<!-- h1-5 represents a header -->
<h1>Hello world!</h1>
<!-- p represents a paragraph -->
<p>Welcome to my counting thing!</p>
<!-- br represents a Line Break (new line) -->
<br />
<!-- You can stick elements in elements too! -->
<p>Visits since last restart:
<!-- p elements can go multiline, but don't display newlines unless you put a "<br />" in them
span elements don't represent anything, and take their style from what they're inside
The id="count" is an attribute, that we will use in our javascript later
It's got a "0" in as that will be the default value. The JS will change this when we add it in.
-->
<span id="count">0</span>
</p>
</body>
</html>You can now open this file in your browser! It should look something like this:

Hmm, looks kind of... Basic. Plus it's not dark-mode friendly; it's so bright! Lets make it look a bit nicer.
/* In CSS, we use this syntax!
element {
thing-to-change: value;
}
*/
/* This sets any HTML elements with the tags <h1></h1> or <p></p>
We'll change the colour of the text inside them to white
By the way, if you're British like me, CSS spells color the American way. */
h1, p {
color: white;
}
/* White-on-white doesn't work. Lets set the background colour too. The grey #373737 looks good!
We set this to any elements with the class attribute set to "background"
Note that . refers to a class. */
.background {
background-color: #373737;
}
/* Make the visit count BIG
Note that # refers to an ID. */
#count {
font-size: 32px;
}
/*There are loads of things we can change in CSS. It's very complex and out of the scope of this tutorial though, so do some research! Google is your friend! */
Yes! That's much easier on the eyes! But it still doesn't do anything yet.
3οΈβ£ Lets make that counter work. We'll need to head out into the server.js file, and copy the following.
// We're working with the server functions
// This brings a library into our code which we will install later!
// ExpressJS is used to route all our requests.
// Variables in javascript begin with "const", "let" or "var".
// Note semi-colons are used to tell it when we're moving to a new line.
let express = require("express");
let app = express();
// We can use the express.static() function which quickly gives us everything in the /public folder
app.use("/", express.static("public"));
// We can also make our own paths which do stuff when we make a request to it!
// For example, lets make our counter endpoint!
// Create a variable that stores how many visits we have had
let visits = 0;
// Create a function which takes a request and response object
function incrementCounter(request, response) {
// The ++ adds 1 to the variable
visits++;
// Send the variable back with a status of 200
// The status is 200 since it's OK. This is similar to those 404 errors you get, but the express library handles those for us :D
response.status(200).send(visits.toString());
// And we're done!
}
// Now recieve "GET" requests at "/counter" (The standard HTTP request you use to access webpages like google)
app.get("/counter", incrementCounter);
// [We're going to insert more code here later!]
// We'll listen on port 3000 - More about this later
// We can also pass a function, so I've added one that tells us
// when it's ready for listening!
app.listen(3000, function() {
console.log("Listening on port 3000!")
});Great! We now have created our server and made a functional counter!
Note: Our counter has been made so it resets every time we restart our server. It is possible to save stuff with these scripts, but can also pose a security threat if not done properly, so I haven't included it in this tutorial :)
Head to your Operating System's terminal or Command Prompt (Tip: In windows, you can press Windows Key + R, type cmd and hit enter to get to it quickly!)
You need to be in the folder with server.js in it, but you may find yourself in your user folder (E.G In windows C:/Users/YourName/). Most OS's follow this command set:
- Use the
cd [Folder]command to navigate into the next folder - Use
cd ..to go back a folder - Use
dirto view the contents of your current folder
C:/Users/Bob> cd Documents
C:/Users/Bob/Documents> cd Website
C:/Users/Bob/Documents/Website>
Once in the correct folder, run npm init -y. This will add a package.json file in your folder, which will contain some basic information about it.
C:/Users/Bob/Documents/Website> npm init -y
Wrote to C:\Users\Bob\Documents\Website\package.json:
{
"name": "Website",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
C:/Users/Bob/Documents/Website>
πββοΈFAQ: "Help! It says "npm" isn't a command!" - Don't worry! You haven't got npm installed! It should install with node.js which you should've installed earlier. Check out this guide.
Now we have established our folder, we can install the express library! Do so with npm install express.
C:/Users/Bob/Documents/Website>npm install express
added 58 packages, and audited 59 packages in 3s
0 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
C:/Users/Bob/Documents/Website>
You should now have a few extra files, so your folder should look like this:
Your folder/
βββ public/
β βββ index.html
β βββ styles.css
β βββ scripts.js
βββ node_modules/
β βββ [Various library folders in here...]
βββ server.js
βββ package.json
βββ package-lock.json
Now with the library installed, we can run it! Try it with node server.js. After a moment, it should say Listening on port 3000!
C:/Users/Bob/Documents/Website>node server.js
Listening on port 3000
πββοΈFAQ: "Help! How do I get out?" Hit Ctrl+C (without anything selected) to escape. Note you'll stop the server by doing this.
πββοΈFAQ: "It's asking to allow access through my firewall!" You should be able to just press cancel. Since it's just running on your home network, it isn't accessible to the outside world, so should be fine for now. We will check this later in this tutorial.
With the server running, you can now visit it in your web browser but this time we'll head to the server's web address. Where is that?
You need to find your local IP address. You can google it, but here's where you can find it in a few OS's:
- In Windows 10+, it can be found in the WiFi properties at the IPv4 section
- In Linux, run
ip -4 addrin the command line - In MacOS, run
ifconfig | grep "inet " | grep -v 127.0.0.1in the command line
In the above, look out for the local IP address. It should look something like 192.168.0.100 but they vary (They should always begin with 192.168.)!
πββοΈFAQ: "Help! I can't find my IP address!" Often google will be your friend! Try researching - There's loads of ways to find it, and the above methods are just examples of one of each.
Once you've found it, use the browser to view http://<YOUR IP>:3000 (E.G http://192.168.0.100:3000) and you should see your site!
But oh no! The counter doesn't go up! π
No worries; lets add that now!
5οΈβ£ Add some functionality to the main webpage. Head into the /public/scripts.js file and copy the following:
// Set our async function to load when the HTML ready
// The async stands for asynchronously - This allows for us to
// pause the function whilst parts are completed.
window.onload = async function () {
// Grab our <span> element using the ID we got earlier.
// NOTE: In HTML IDs can only be matched to one element,
// otherwise we get errors :)
let counterElement = document.getElementById("count");
// Now we need to make the request to our /counter endpoint!
// We use a built-in function called "fetch()" to do this
// Since it takes some time, we add the "await" on, which the "async" above allows us to do.
let response = await fetch("/counter");
// We have a response, but we need the data we sent!
// To get it, we use the response.text() which needs an await
let visitCount = await response.text();
// Awesome! We now have the number in our visitCount variable!
// Now we need to display it. We can set it as the text inside
// the <span> variable we got earlier!
// innerText is a property (like a variable),
// so we set it with an equals sign
counterElement.innerText = visitCount;
}Now head back to your terminal. Hit Ctrl+C to stop the server. Then start it again.
Listening on port 3000
^C
C:/Users/Bob/Documents/Website>node server.js
Listening on port 3000
Heading back to the webpage (It'll want a refresh), the counter now works! Refresh it a few times and you should see the counter go up!
Head back to the files in the public folder, and change them up! Have fun and make the website your website and not just a random visitor counter website. This tutorial barely touches on what you can do with HTML, JS and CSS!
Good learning resources include (but are not limited to):
- W3Schools
- GeeksForGeeks
- Google Search (Or whatever search engine you prefer)
You may have noticed that visiting the page on your phone also works, but only while you're on the same WiFi network, which isn't ideal.
In this section of the tutorial, we'll go through how to allow access to your website outside of your home network!
- You can only host one website per port per network.
- You will need Administrator Access to the router.
- You'll need to be able to change your firewall settings. More on this later!
This section of the tutorial will involve working with the Router. By default, the router blocks all requests into your home network (that would be a massive security issue). However, we can allow some routes in to stop any bad requests, but keep it safe and secure!
We do this with Port Forwarding. As you probably saw before, we listened to our website through port 3000, and connected to it through http://YourIp:3000 (the 3000 at the end is the port). The port is what allows us to connect to the server!
Port forwarding allows us to pick ports to allow inbound requests through the router!
Access your router through your browser (typically at http://192.168.0.1). You'll need to enter the admin password to access the settings
βNote: Routers come in different shapes and sizes, and their software tends to be different too. There isn't any standard, but look for "Port Forwarding", which is usually in the security section.
In your Port Forwarding settings, add a new configuration. Use the below settings and add your IP address (same as the one you used to connect to the website).
| Parameter | Input |
|---|---|
| IP Address | Your IP (192.168...) |
| Internal Start Port | 3000 |
| Internal End Port | 3000 |
| Start Port | 80 |
| End Port | 80 |
| Protocol | TCP |
π€What's happening here?
When we set up the server, we made it listen to port 3000. However, this is not the standard HTTP port (which is port 80). One issue with some programs is that other services are sometimes automatically served on port 80. To avoid conflicts, we will set it up on Port 3000.
However, to allow your website to continue as the standard http port, we will route the outside port 80 to your server port 3000.
Later, we will also allow your website to become secure using https. However, this requires a separate port (443). This tutorial will take you through the process later, but in preparation we will set up the port now. Note that we set up the secure server on the port 3001.
Add a new configuration with the below settings:
| Parameter | Input |
|---|---|
| IP Address | Your IP (192.168...) |
| Internal Start Port | 3001 |
| Internal End Port | 3001 |
| Start Port | 443 |
| End Port | 443 |
| Protocol | TCP |
Firstly, head over to WhatIsMyIPAddress and grab the IPv4 address. This is the address that connects to your router. Make sure to keep note of this - You'll need it later (though, you can always access it on any device on your home network)!
On another device (like a phone or tablet), disconnect from the current network and join another (for example, another WiFi hotspot or mobile (cellular) data).
πββοΈFAQ: "I don't have any mobile/cellular data or another hotspot!" You don't need to disconnect to test this. It is just highly recommended to prove that it does work.
On the other network, (on a browser) head to http://[TheNewIPAddress] replacing [TheNewIPAddress] with the IP you just got from WhatIsMyIPAddress. You should now see the website you made earlier!
πββοΈ FAQ: "Help! I can't connect to my website!" You probably need to configure your firewall. In windows settings, search for "Windows Defender Firewall", then click Advanced Options, Inbound rules, then on the right click "Add Rule". On all systems where this is needed, allow TCP ports 3000 and 3001. If this is the case, you're one step ahead!
Lots of OS's come with a firewall pre-installed. Often, you just need to switch it on. If yours doesn't come with a firewall, it is highly recommended that you install one.
- Switch ON your firewall (if not already done)
- Configure it to allow inbound access ports 3000 and 3001 (otherwise you won't be able let people access your website!)
- Save and continue (on some systems you might need to restart).
Congrats! Your website is now available to the general public! π
But, that IP Address you use to connect to doesn't look all that pretty, and people won't want to go to a website that looks dodgy. In the next section, we'll learn to...
In this part of the tutorial, we'll learn about domains, what they are, and how you get one!
In a full URL, you'll find the following parts:
`https://login.example.com/index.html#section-two?value=5
https://- This is the protocol which defines the type of data you wantlogin.- This is the subdomainexample.com- This is the domain. Specifically the Top-Level Domain (TLD)./index.html- This is the path.#section-two- This is the hash, which is typically used for auto-scrolling to HTML IDs in most browsers?value=5- This is the query, and is often used for passing data in a URL.
In this section, we will be focussing on the subdomain and domain/TLD parts of the URL. We'll be using a free online service called FreeDNS to give us a nice URL.
βNote: Already have a domain in mind? You'll probably need to buy it from a service. Domains can be expensive, so it's always a good idea to shop around. All domain services are different, so you should follow their guide to installing!
In order to control your domains, you'll need to sign up for an account. Head over to FreeDNS and sign up for an account.
Head over to the FreeDNS Domain Registry and choose a domain that you like!
Ensure you choose carefully - You can change it later, but this will be what everyone uses to connect to your site!
Considerations:
- Choose a trustworthy domain. See this notice for info on how to do so
- Often the shorter the domain, the more memorable!
- Fun URLs are totally fine!
- URLs should ideally be family-friendly!
- Choose "Public" domains for faster/guaranteed access
Once you've found one, click on the domain name.
Clicking on the domain name should display a form. Input the following information in it:
| Parameter | Input |
|---|---|
| Hostname | The IP address you got from WhatIsMyIPAddress (may be auto-filled) |
| Type | A |
| Domain | Leave as it is (The one you selected in the last screen) |
| Subdomain | Get creative! This comes before the domain you chose |
Click submit! The request has been processed. If you chose a "private" domain, you need to wait for approval from the domain owner. Otherwise, wait a few minutes (to an hour) for it to process.
Head over to http:// + the subdomain you inputted + the domain you chose in your browser! When you see your website, it has processed!
Awesome! You now have a working website with an actually readable URL!
However, if you try to connect with the modern standard protocol https://, it won't work. Let's fix that!
Connecting with http:// is fine, but if you need to transfer data securely, and want to ensure that the data is encrypted, you need https://.
In order to enable encryption, we need to use the following:
- An SSL certificate to encrypt/decrypt the data
- Another port to listen for https requests over
We need to add a little more code to our server file to accomodate the changes. Head back over to the /server.js file. On the line that says // [We're going to insert more code here later!], add in the below code:
// We need this so we can validate the SSL cert - This is used later :)
app.use(express.static(".well-known/acme-challenge"));
// This sets up the server for the HTTPS bit.
/* Remove this whole line when I say to!
require("https").createServer({
key: require("fs").readFileSync(""),
cert: require("fs").readFileSync("")
}, app).listen(3001);
console.log("https on port 3001");
Remove this whole line too, when I say to!*/Restart your server in the command line by using Ctrl+C and node server.js again.
In this tutorial, we use CertBot + LetsEncrypt to generate free SSL certificates.
Head to the CertBot Website and scroll down to the System and Software input boxes. Choose your system and Other software. Follow the install instructions.
When prompted to run certbot, (using the command like certbot certonly, ensure you choose the command with webroot in it.)
Set the domain to subdomain + domain that you put together earlier in FreeDNS.
πββοΈFAQ: "Help! It says I don't have the right permissions!" This usually means you just need to run it as administrator, or in sudo if you're on linux.
Set the webroot as the folder that server.js is in:
Your folder/ <== This folder!
βββ public/
β βββ index.html
β βββ styles.css
β βββ scripts.js
βββ node_modules/
β βββ [Various library folders in here...]
βββ server.js
βββ package.json
βββ package-lock.json
πββοΈFAQ: "Help! It can't connect!" This could be the issue of a few things:
- The firewall is blocking the connection.
- The port the server is running on hasn't been port-forwarded.
- The WebRoot mode hasn't been selected, or has been set in the wrong place
Note you only have 5 failed requests per hour! Be careful!
CertBot will now have saved your certificate and key to a location on your drive (often in a temp folder) and will log to the command prompt the location.
C:/Users/Bob/Documents/Website> certbot certonly --webroot
Saving debug log to /letsencrypt/letsencrypt.log
Requesting a certificate for example.example.com
Input the webroot for example: (Enter 'c' to cancel): C:/Users/Bob/Documents/Website
Successfully received certificate.
Certificate is saved at: /letsencrypt/live/example.example.com/fullchain.pem
Key is saved at: /letsencrypt/live/example.example.com/privkey.pem
This certificate expires on 1970-01-01.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
C:/Users/Bob/Documents/Website>
Copy the paths to the certificate and key. Note we won't be moving the actual files, as CertBot will automatically update just before they expire.
Back in server.js, lets finish the https setup. In this section:
/* Remove this whole line when I say to!
require("https").createServer({
key: require("fs").readFileSync(""),
cert: require("fs").readFileSync("")
}, app).listen(3001);
console.log("https on port 3001");
Remove this whole line too, when I say to!*/Remove the two lines:
/* Remove this whole line when I say to! and Remove this whole line too, when I say to!*/.
In the line key: require("fs").readFileSync(""),, add the file path to the key (not the certificate!) between the two speech marks in readFileSync("[In Here!]"). Do the same for the certificate in the line beginning with cert.
Once done, your code should look something like this:
require("https").createServer({
key: require("fs").readFileSync("/letsencrypt/live/example.example.com/privkey.pem"),
cert: require("fs").readFileSync("/letsencrypt/live/example.example.com/fullchain.pem")
}, app).listen(3001);
console.log("https on port 3001");Of course, with your paths instead of mine.
Make sure you restart the server, by using Ctrl+C and node server.js. This time it should also say that an https server is running on port 3001.
Listening on port 3000
^C
C:/Users/Bob/Documents/Website>node server.js
Listening on port 3000
https on port 3001!
Now, head back over to your web browser, and put in https://[YourSubdomain + Domain] adding your subdomain and domain like how you connected earlier. Your site should now be secure when connecting with HTTPS!
- These certificates expire! CertBot should automatically renew them, but in the even of an error, follow the same steps as above again. You'll be able to tell if you've kept an expired key/certificate if your browser shows a security warning.
- Of course encryption is not available across the whole world. That's why we left the
httpserver running (The one on port 3000)
If you managed to keep up with all the above steps, congratulations! You now have a fully functioning website! π
Unfortunately, where making two websites on one network is 100% possible, you will find some limitations to two websites.
- One website will be accessible from the other subdomain
- You will need new ports externally, (and internally if on the same machine) so probably, which are limited between 1 and 65535.
- You will need to connect to it using the
:PORTon the end of the URL, likehttps://some-other.domain.com:PORT.
In this tutorial, we learned:
- How to create webpages with HTML, CSS and Javascript
- How to create a server in server-side javascript (Node.JS) with routing
- How to publish our website to the wider internet
- How to set up a subdomain with our server
- How to set up https on our subdomain
I hope you enjoyed following this tutorial! It would be awesome if you could star this repo if you found it useful!
Have your say! Found an issue (security/general bug), or wish to add a bit more info? Drop a pull request/issue in this repo and I'll try to get it sorted/approved ASAP!
- FreeDNS (Free Subdomains that are often TLD-like): https://freedns.afraid.org/
- CertBot (CLI for LetsEncrypt): https://certbot.eff.org/
- LetsEncrypt (Non-Profit organisation giving out TLS certificates): https://letsencrypt.org/
- W3Schools (Free programming learning platform): https://www.w3schools.com/
- NodeJS (Server-side Javascript): https://nodejs.org/
- NPMJS (Javascript library/package provider): https://npmjs.com
- ExpressJS (Node.JS Web Application Framework): https://expressjs.com/
- World encryption laws map (GP-Digital): https://www.gp-digital.org/world-map-of-encryption/
Note: The code in the code-dump folder is openly available! Feel free to do whatever you want with it - No small prints!
Last update: 27 May 2023 @ 16:19PM