Port Forwarding with xinetd – a flexible solution under Linux

An abstract picture showing computers and servers which are connected. In between is sitting a xinetd process forwarding requests on port 443 to a server running on port 8443

In Linux systems, binding a non-root process to a privileged port (which are ports <1024) is not allowed by default. This can be inconvenient when you need to run a server on a standard port without root privileges. xinetd offers a solution to this problem by providing a flexible port forwarding mechanism.

In my case, I had to set it up because I wanted to run a non-root server on external port 443 and I didn't want to start it with root privileges.

Key reasons to use xinetd

  • Bind non-root processes to privileged ports
  • Enhance security by avoiding running server processes as root
  • Provide a workaround when running a process with root privileges is not possible or advisable

Installing xinetd (I did this on Ubuntu 22.04)

sudo apt-get update
sudo apt-get install xinetd

Configuring forwarding

First, create a configuration file for your service. In my case, I did set up port forwarding for a ThingsBoard HTTPS server listening on port 8443 (localhost only). xinetd is configured to listen on (external) port 443 and is forwarding traffic to the (internal / localhost-only) port 8443.

Diagram showing the xinetd configuration which is forwarding traffic from external port 443 to a process listening on localhost:8443
xinetd configuration: forwarding traffic from external port 443 to a process listening on localhost:8443

You can use nano (or pico, vi, vim, etc.) to edit the configuration. To add a configuration place it in 

sudo nano /etc/xinetd.d/thingsboard-https 

Name your config file according to your preferences and needs. I named mine according to the server-process name, but basically anything else is also fine.

service thingsboard-https
{
  disable                 = no
  flags                   = REUSE
  wait                    = no
  user                    = root
  socket_type             = stream
  protocol                = tcp
  port                    = 443
  redirect                = localhost 8443
  log_on_success  -= PID HOST DURATION EXIT
}
Editing xinetd config in nano to configure external port forwarding from 443 to localhost:8443
Editing xinetd config in nano to configure external port forwarding from 443 to localhost:8443

Brief config parameter explanation

  • disable = no: enables the service
  • flags = REUSE: allows reuse of the socket
  • wait = no: xinetd will start a new process for each connection
  • user = root: service runs as root (necessary for binding to port 443)
  • socket_type = stream: uses TCP
  • protocol = tcp: specifies TCP protocol
  • port = 443: port xinetd will listen on
  • redirect = localhost 8443: forwards connections to localhost on port 8443
  • log_on_success -= PID HOST DURATION EXIT: reduces logging verbosity

Adding the "new" service to system services

sudo nano /etc/services

Add / Edit the following line, name it according to your service name you just used above (with xinetd):

https      443/tcp      thingsboard-https    # http protocol over TLS/SSL

This step ensures that the service name is recognized system-wide and can be referenced by other applications. By default https 443/tcp , it is already set but is used with another service name.

Save this config and restart your server (restarting xinetd only is enough in most cases). Anyhow, I recommend restarting the whole server by doing a sudo reboot now.

sudo systemctl restart xinetd
# or
sudo reboot now

Verifying the configuration

To verify the configuration, I am using the ss command (short for socket statistics) to show listening sockets on my machine:

sudo ss -tulp | grep https
# or
sudo ss -tulpn | grep 443

We see the output indicating that xinetd is listening on port 443 (https). Everything is working as expected!

Here is a brief reminder of what the parameters we passed to ss do:

  • -t: display TCP sockets
  • -u: display UDP sockets, when used together with -t, it will display both TCP and UDP sockets
  • -l: stands for "listening" and shows only sockets that are listening for incoming connections
  • -n: prevent DNS lookups, which can speed up the command by not resolving IP addresses to hostnames
  • -p: show the PID and name of the program to which each socket belongs