Sometimes development is like this; the problem isn't something you can just find, but rather something that "accidentally" comes to you. This time, we "hit the jackpot":
Originally, we just wanted to change port 80 to an internal port, and to save time, we casually added +100 to make it 10080. Unexpectedly, this "random choice" happened to hit the blocked port blacklist of Node.js fetch. 😂
So the situation became quite surreal:
At first, we thought it was either the service not starting or a bug in Node.js. After thorough investigation, we discovered that this was actually a rule set by the Fetch standard, not an issue with our code.
In this article, I will take you through this pitfall: why is Node.js fetch blocked by the port? Why does the http module work fine? How should you handle similar issues?
First, let's look at a minimal reproduction:
import http from "node:http";
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "hello" }));
});
server.listen(10080, "127.0.0.1", async () => {
console.log("✓ Server started on http://127.0.0.1:10080");
// Using Node.js fetch
try {
const res = await fetch("http://127.0.0.1:10080/test");
console.log("fetch() result:", await res.text());
} catch (err) {
console.error("fetch() failed:", err.message, err.cause?.message);
}
// Using http.request
http.get("http://127.0.0.1:10080/test", (res) => {
res.on("data", (chunk) =>
console.log("http.request() result:", chunk.toString())
);
});
});
Running result:
✓ Server started on http://127.0.0.1:10080
fetch() failed: fetch failed bad port
http.request() result: {"message":"hello"}
Did you see that? The same port, fetch crashes, but the http module works smoothly. This basically confirms that the issue is not on the server side, but with fetch itself.
Forced to check various documents, I finally found the answer in the WHATWG Fetch Standard!
It turns out that fetch has a built-in port blocking list for security reasons, which directly rejects access to these ports.
Common blocked ports include:
Port Number | Service | Is Blocked by fetch |
---|---|---|
25 | SMTP Mail Service | ✅ |
110 | POP3 Mail Service | ✅ |
143 | IMAP Mail Service | ✅ |
6667/6697 | IRC Chat Service | ✅ |
6000 | X11 | ✅ |
10080 | Amanda Backup Service | ✅ |
Thus, the built-in fetch in Node.js (based on undici) faithfully implements the standard, throwing an error when accessing these ports:
TypeError: fetch failed
Cause: bad port
Meanwhile, the http module doesn't care about this at all, resulting in fetch saying "no," while the http module says "no problem."
Since this is a "hidden rule" set by the standard, the solution is quite simple:
Change the port if possible
Avoid these blacklisted ports, for example, use 3000, 8080, 18080, etc.
Can't change the port? Then change the tool
If you must access these ports, do not use fetch for the request; you can switch to:
Example:
import http from "node:http";
http.get("http://127.0.0.1:10080/test", (res) => {
res.on("data", (chunk) => console.log(chunk.toString()));
});
Don't think about bypassing the standard
Node.js does not have a switch to turn off this restriction because it is a regulation of the Fetch Standard.
This was a "happy accident" discovery:
In short, we were quite "lucky" this time: we just wanted to save time by changing 80 to 10080, and ended up hitting the blacklist. It's like playing the lottery — we did win, but the prize was a bad port error. 🎁😂
👉 Next time you encounter a strange fetch failed, don’t doubt your life first; you might just have hit the jackpot too.
Got any insights on the content of this post? Let me know!