You can save this page as a PDF in your browser (Print → Save as PDF) after you’ve opened it locally.
0. Overview
This guide will help you turn a Windows 10 PC into a small server using WSL2 and Ubuntu. You will end up with:
- A Linux environment (Ubuntu) running inside Windows 10
- Nginx + PHP + MySQL (LAMP-style stack)
- Node.js server with WebRTC signaling
- Nginx reverse proxy in front of Node and PHP
- Automatic Windows port forwarding to WSL2
- LAN access, and later, safe internet exposure
1. Prepare Windows 10
1.1 Update Windows
- Click the Start button.
- Click Settings (gear icon).
- Go to Update & Security → Windows Update.
- Click Check for updates and install everything important.
1.2 Enable WSL and Virtualization
- Click Start, type PowerShell.
- Right-click Windows PowerShell → Run as administrator.
- Paste and run the following commands one by one:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
- Restart your PC when prompted.
1.3 Install WSL2 Kernel and Set Default Version
- Open PowerShell as administrator again.
- Run:
wsl --update
wsl --set-default-version 2
2. Install Ubuntu in WSL2
2.1 Install Ubuntu from Microsoft Store
- Open the Microsoft Store.
- Search for Ubuntu 22.04 LTS.
- Click Get or Install.
- After installation, click Launch.
2.2 First Launch Setup
- A terminal window will open with Ubuntu starting.
- It will ask for a UNIX username (e.g.
darryl). - It will ask for a password (this is your Linux admin password).
2.3 Update Ubuntu
In the Ubuntu window, run:
sudo apt update
sudo apt upgrade -y
2.4 Create Project Folders
Still in Ubuntu, create folders for your server:
mkdir -p ~/server/{web,node,logs}
3. Install Nginx, PHP, MySQL, Node
3.1 Install Nginx, PHP, MySQL
sudo apt install -y nginx mysql-server php-fpm php-mysql
3.2 Secure MySQL
sudo mysql_secure_installation
Recommended answers:
- Set root password: Yes (choose a strong one)
- Remove anonymous users: Yes
- Disallow root remote login: Yes
- Remove test database: Yes
- Reload privilege tables: Yes
3.3 Install Node.js via nvm
sudo apt install -y curl build-essential
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
node -v
npm -v
4. Configure Nginx + PHP + Reverse Proxy
4.1 Create Web Root and Test PHP File
mkdir -p ~/server/web/public
echo "<?php phpinfo(); ?>" > ~/server/web/public/index.php
4.2 Nginx Site Configuration
Replace YOURUSER with your Ubuntu username.
sudo nano /etc/nginx/sites-available/app.conf
server {
listen 80;
server_name _;
root /home/YOURUSER/server/web/public;
index index.php index.html;
access_log /home/YOURUSER/server/logs/nginx_access.log;
error_log /home/YOURUSER/server/logs/nginx_error.log;
# Admin panel (PHP)
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
# Node.js API
location /api/ {
proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebRTC signaling (WebSocket)
location /signal/ {
proxy_pass http://localhost:3001/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
Save and exit (Ctrl+O, Enter, Ctrl+X).
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx
5. Node + WebRTC Signaling Server
5.1 Create Node Project
cd ~/server/node
npm init -y
npm install express ws
5.2 Create server.js
const express = require('express');
const WebSocket = require('ws');
const app = express();
const apiPort = 3000;
const signalPort = 3001;
// Basic API
app.get('/api/test', (req, res) => {
res.json({ status: "API OK" });
});
app.listen(apiPort, () => {
console.log(`API running on ${apiPort}`);
});
// WebRTC signaling
const wss = new WebSocket.Server({ port: signalPort });
wss.on('connection', ws => {
ws.on('message', msg => {
// Broadcast signaling messages
wss.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(msg);
}
});
});
});
console.log(`Signaling server running on ${signalPort}`);
Run it:
node server.js
6. Simple PHP Admin Panel
6.1 Replace index.php with Admin Panel
nano ~/server/web/public/index.php
<?php
$info = [
"server_time" => date("Y-m-d H:i:s"),
"php_version" => phpversion(),
"mysql_status" => trim(shell_exec("systemctl is-active mysql 2>/dev/null")),
"node_api_test" => @file_get_contents("http://localhost/api/test")
];
?>
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
pre { background: #eee; padding: 10px; }
</style>
</head>
<body>
<h1>Admin Panel</h1>
<pre><?php print_r($info); ?></pre>
</body>
</html>
Test from inside Ubuntu:
curl http://localhost
Then from Windows, open a browser and go to:
http://localhost
7. Windows Firewall and Port Forwarding (LAN Access)
7.1 Allow Ports in Windows Firewall
- Open Start → type Windows Defender Firewall with Advanced Security → open it.
- Click Inbound Rules → New Rule....
- Select Port → Next.
- TCP, Specific local ports:
80→ Next. - Allow the connection → Next.
- Check only Private for now → Next.
- Name it WSL_HTTP_80 → Finish.
Repeat the same steps for ports 3000 and 3001 (Node API and signaling).
7.2 Find WSL IP
ip addr show eth0
Look for a line like inet 172.24.x.x/20. That IP is your WSL IP.
7.3 Manual Port Forwarding (First Time)
In PowerShell as administrator:
$wslIP = "YOUR_WSL_IP_HERE"
netsh interface portproxy add v4tov4 listenport=80 listenaddress=0.0.0.0 connectport=80 connectaddress=$wslIP
netsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=$wslIP
netsh interface portproxy add v4tov4 listenport=3001 listenaddress=0.0.0.0 connectport=3001 connectaddress=$wslIP
From another device on your LAN, open a browser and go to:
http://<your-windows-LAN-IP>→ Admin panelhttp://<your-windows-LAN-IP>/api/test→ Node API
8. Auto‑Detect WSL IP and Re‑Apply Portproxy Rules
WSL2 changes its internal IP address every time Windows reboots. This means your port forwarding breaks unless you update it manually.
This step creates a PowerShell script that:
- Detects the current WSL IP
- Clears old portproxy rules
- Re‑adds the correct rules for ports 80, 3000, and 3001
8.1 Create the PowerShell Script
1. Press Windows Key + E to open File Explorer.
2. Open your C:\ drive.
3. Right‑click → New → Text Document.
4. Name it: wsl-portproxy.ps1
(Make sure it ends in .ps1, not .txt.)
5. Right‑click the file → Edit (or open in Notepad).
6. Paste this inside:
$wslIP = wsl.exe hostname -I | ForEach-Object { $_.Split(" ")[0] }
# Clear old rules
netsh interface portproxy reset
# HTTP
netsh interface portproxy add v4tov4 listenport=80 listenaddress=0.0.0.0 connectport=80 connectaddress=$wslIP
# Node API
netsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=$wslIP
# WebRTC signaling
netsh interface portproxy add v4tov4 listenport=3001 listenaddress=0.0.0.0 connectport=3001 connectaddress=$wslIP
7. Save and close Notepad.
8.2 Test the Script
1. Click Start, type PowerShell.
2. Right‑click → Run as administrator.
3. Run:
powershell -ExecutionPolicy Bypass -File "C:\wsl-portproxy.ps1"
4. If no errors appear, the script works.
8.3 Make It Run Automatically at Boot
1. Press Start, type Task Scheduler, open it.
2. Click Create Task (right side).
3. Name it: WSL Portproxy Auto.
4. Check: Run with highest privileges.
5. Go to the Triggers tab → New → choose At startup.
6. Go to the Actions tab → New.
7. Program/script:
powershell.exe
8. Add arguments:
-ExecutionPolicy Bypass -File "C:\wsl-portproxy.ps1"
9. Save the task.
Now your WSL ports will always work after reboot.
9. Securing Your Stack Before Internet Exposure
Before opening anything to the outside world, lock down the basics.
9.1 Secure MySQL
- MySQL must only listen on
127.0.0.1 - Never expose port 3306 to the internet
- Use a dedicated MySQL user for your app
Edit MySQL config:
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
Find the line:
bind-address = 127.0.0.1
If it’s commented out, uncomment it.
Save → restart MySQL:
sudo systemctl restart mysql
9.2 Secure Nginx
- Disable directory listing
- Ensure PHP files cannot be downloaded
- Use HTTPS when exposing publicly
9.3 Secure Node/WebRTC
- Never expose Node directly
- Always proxy through Nginx
- Use rate limiting later if needed
10. Adding HTTPS (Let’s Encrypt)
This step is optional until you expose your server publicly.
10.1 Install Certbot
sudo apt install certbot python3-certbot-nginx -y
10.2 Run Certbot
sudo certbot --nginx
Follow the prompts:
- Enter your domain name
- Agree to terms
- Choose redirect to HTTPS
Your site is now HTTPS‑secured.
11. Exposing Your Server to the Internet
Once everything works on LAN and is secured, you can expose it publicly.
11.1 Router Port Forwarding
Forward these ports from your router to your Windows PC:
- 80 → 80
- 443 → 443
Do not forward 3000 or 3001.
11.2 Test from Mobile Data
Turn off Wi‑Fi on your phone and visit:
https://your-domain.com
If it loads, your server is live.
12. Daily Workflow and Useful Commands
12.1 Start Node Server
cd ~/server/node
node server.js
12.2 Restart Nginx
sudo systemctl restart nginx
12.3 Check WSL IP
hostname -I
12.4 View Logs
tail -f ~/server/logs/nginx_error.log
Your server stack is now complete and production‑ready.