IP-Based Service Multiplexing

Markus | Sunday, March 30th 2025, 09:40

-- How to host both a web server and VPN on port 80

Figure 1. Pictogram of a server responding differently on the same port depending on IP address

Recently, I had to configure one of my servers to handle requests coming from a specific IP differently from the rest of the Internet. Specifically, I wanted port 80 to usually return my website, but for one specific client (based on the IP address), I wanted it to instead forward to request to another server running SSH. (Yes, that's kind of stupid, but drastic firewalls require drastic measures.)

In the following, I will show how to configure iptables to change the backend service handling a connection based on the source IP address. I'll give a quick example using a simple HTTP server using Python and one socat TCP endpoint serving the current date and time over raw TCP. I am intentionally choosing two incompatible protocols here to show that this method allows this.

Setting Up Services

First, we need to configure our two (or more) services that will be handling requests. Good news here, you barely need to do anything special! For the "main" service that the public is allowed to access, configure it like normal, with the public facing port as its listening port (e.g. 8080). Then, for the second service that's only for specific IP addresses, do what you usually would, but make it listen on a different port (e.g. 1234).

main_service.py:

#!/usr/bin/env python

from http.server import BaseHTTPRequestHandler,HTTPServer

class hndl(BaseHTTPRequestHandler):
	def do_GET(self):
		self.send_response(200)
		self.end_headers()
		self.wfile.write(b"Hello Internet")

HTTPServer(('0.0.0.0', 8080), hndl).serve_forever()

hidden_service.sh:

#!/usr/bin/bash

socat TCP4-listen:1234,reuseaddr,fork EXEC:'date'

Now start and quickly test your services:

markus@server:~$ curl "http://localhost:8080"
Hi Internet!
markus@server:~$ nc localhost 1234
Sat Mar 29 10:23:35 AM CET 2025

Configuring the Firewall

Next up, we need to configure the firewall to a) allow incoming requests to our services, and b) treat requests coming from that one special IP address differently. Default requests should just be served, and the special IP needs to be redirected (transparently) to the other port.

iptables -A INPUT -p tcp -m tcp --dport 8080 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 1234 -j ACCEPT
iptables -t nat -A PREROUTING -i <uplink-interface> -p tcp --dport 8080 --src <insert-special-ip-here> -j REDIRECT --to-port 1234

Testing It

And then it's time to test it from different machines. The host named "special" has the IP that we applied the PREROUTING configuration above. Note that both devices access the server on port 8080.

markus@internet:~$ nc server.local 8080
GET /

Hi Internet!
markus@special:~$ nc server.local 8080
Sat Mar 29 10:41:06 AM CET 2025

As you can see, the "random" machine from the Internet had to supply a normal GET request and got the "Hi Internet!" HTTP response. The "special" host, on the other hand, immediately got the current date and time as a response, showing that its connection never reached the Python script, but hit the socat server instead.

There are some improvements that could be made to this setup, such as not allowing external requests directly to the second service, but this should be provide a quick starting point for this kind of setup. As usual, if you have any comments or suggestions, feel free to contact me via email or other means of communication.

Tags: linux networking software