When you need to check your internet speed, you probably head to Ookla or Fast.com. They're household names for a reason, but they have one limitation: they test your connection to their servers, not yours. For anyone running their own infrastructure, that's a critical blind spot. This gap is exactly why I built SamNet Speedtest—a fully self-hosted tool that gives me the ground truth about my own network's performance.
This project wasn't just about creating another portfolio piece. It was a deep dive into the nuts and bolts of network performance, a challenge in secure backend design, and a fun exercise in creating a polished user experience from scratch.
Why Does a Self-Hosted Speed Test Matter?
In IT and cybersecurity, knowing your true bandwidth is fundamental. Commercial services are a good baseline, but a self-hosted test answers more specific, critical questions:
- Measure quality of service: Knowing if your connection is stable and fast.
- Reveal bottlenecks: A server might have great hardware but poor bandwidth.
- Benchmark infrastructure: Useful when you're running your own services and want to verify performance.
- Validate end-user experience: If you're hosting a game server, a media library, or a web app for friends, you need to know what performance they're actually getting from your machine.
By building my own speed test, I created a definitive way to validate my hosting environment, from ISP bandwidth and server tuning to firewall throughput, while also providing a public tool others can use to test their connection to my corner of the internet.
The Unexpected Hurdles
Building a speed test sounds simple: upload and download a file, time it, and do some math. The reality, I discovered, is a minefield of browser quirks, server configurations, and security considerations.
File Hosting for Download Tests
To measure download speed accurately, the file must come directly from the server's disk to the user, bypassing any caches along the way. I created a large binary file using /dev/urandom and then had to wrestle with Nginx. The key was setting the Cache-Control: no-store header to prevent browsers or proxies from cheating the test, and disabling Nginx's own buffering with X-Accel-Buffering: no to ensure a direct, unmediated stream of data.
Handling Uploads Securely
Accepting large, arbitrary binary data from the internet is inherently risky. My upload.php endpoint needed to be robust enough to handle a stream of data without loading the entire file into memory and crashing PHP. By reading the input stream in small 8KB chunks, the server could process uploads of any size with a tiny memory footprint. Disabling request buffering in Nginx and setting up the right CORS headers were crucial to making it work smoothly and securely.
Browser Memory Limits
My first attempt at the upload test involved generating a massive data blob in the browser and sending it all at once. This promptly crashed the browser tab with a Failed to execute 'getRandomValues' error. Modern browsers have limits on how much random data can be generated at once to prevent malicious scripts from hogging system resources. The solution was to mirror the backend's logic: generate and send the data in smaller, manageable chunks, turning the test into a true data stream.
Crafting the User Experience
A speed test is a performance tool, but it's also a user experience. Nobody wants to stare at a static page wondering if it's working. It needed to feel alive and responsive. I spent significant time implementing the animated progress circles, the smoothly updating speed bars, and the real-time logs. This live feedback isn't just for show—it builds trust and gives the user a clear sense of what's happening behind the scenes, making the tool feel professional and reliable.
The Nuances of Measurement
Simply dividing file size by time isn't enough for a great speed test. Network speeds fluctuate. To provide a more accurate and insightful result, I implemented live calculations. The logs show rolling updates like "Transferred 15 MB in 2.1s → 57.14 Mbps," giving a real-time view of the connection's stability and performance, not just a final average.
The Solution: A Three-Part System
The final version of SamNet Speedtest is a synergistic system of three core components working in harmony:
- The Frontend: A clean, responsive interface built with HTML5 and Tailwind CSS. It's designed to provide instant, visually intuitive feedback, with progress circles that fill up, bars that animate, and a final results popup that's easy to read and share.
- The Backend: Two highly specialized endpoints. A static
download.binfile served by a carefully tuned Nginx location, and a leanupload.phpscript designed for one job: to catch data streams efficiently without overwhelming the server. - The User Experience Layer: Small but crucial features that elevate the tool, like detecting the user's location via an IP lookup for context, the live log that shows the test's progress, and a clean results summary.
The entire project is wrapped in a layer of security, served exclusively over HTTPS via Nginx, ensuring all test data is encrypted in transit.
A Simple How-To
If you're inspired to build your own self-hosted speed test, here's a blueprint to get you started:
1. Create a test file:
# Creates a 100MB file of random data
dd if=/dev/urandom of=/var/www/html/speedtest/download.bin bs=1M count=100
2. Create an upload handler (upload.php):
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Content-Range, Content-Disposition, Content-Description');
$input = fopen('php://input', 'rb');
$bytes = 0;
while ($chunk = fread($input, 8192)) {
$bytes += strlen($chunk);
}
fclose($input);
echo json_encode(["ok"=>true,"received_bytes"=>$bytes]);
?>
3. Add Nginx config tweaks:
# Nginx site config snippet
# Disable caching and buffering for the download file
location /speedtest/download.bin {
add_header Cache-Control "no-store";
add_header X-Accel-Buffering "no";
}
# Disable request buffering for the PHP upload handler
location /speedtest/upload.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_request_buffering off;
}
The frontend JavaScript streams data to/from these endpoints, measures time, and updates the UI. That's it — you now have a functional, self-hosted speed test.
What This Project Taught Me
Beyond being a cool tool, this project was a powerful learning experience that solidified my skills in:
- Full-Stack Development: Juggling a vanilla JavaScript frontend, a PHP backend, and the underlying Nginx infrastructure provided a holistic view of web application architecture.
- Pragmatic Problem Solving: I didn't just follow a tutorial; I diagnosed and fixed real-world issues like browser entropy limits, Nginx buffering quirks, and inefficient PHP memory handling.
- Security-First Mindset: From serving everything over HTTPS to designing an upload handler that resists resource exhaustion, security was a core consideration, not an afterthought.
- Building for the User: It reinforced that a project's value isn't just in its function but in its presentation. A good UI and clear feedback can make all the difference.
Final Thoughts
Building SamNet Speedtest was a journey through multiple layers of technology, from low-level server configuration to high-level user interface design. It was a fantastic opportunity to merge my knowledge of infrastructure, web development, and cybersecurity into a practical, tangible tool that people can actually use.
I invite you to try it yourself at samnet.dev/speedtest. This project wasn't just about measuring internet speeds. It was about building confidence in my own infrastructure, tackling real-world engineering challenges, and creating a tool that successfully balances performance, security, and user experience.