{"id":2914,"date":"2026-04-28T17:51:12","date_gmt":"2026-04-28T16:51:12","guid":{"rendered":"https:\/\/www.balajibandi.com\/?p=2914"},"modified":"2026-04-29T07:37:09","modified_gmt":"2026-04-29T06:37:09","slug":"building-a-modern-web-based-serial-console-server","status":"publish","type":"post","link":"https:\/\/www.balajibandi.com\/?p=2914","title":{"rendered":"Building a Modern, Web-Based Serial Console Server"},"content":{"rendered":"\n<p>In the world of network engineering, the &#8220;out-of-band&#8221; (OOB) management link is our last line of defence. When a remote router loses its routing table or a core switch panics, SSH is the first thing to go. Historically, this meant a physical trip to the data centre or comms room with a laptop and a turquoise Cisco console cable. This is may lead to Larger downtime and time take to travel to far places.<\/p>\n\n\n\n<p>Today, we can build a high-availability&nbsp;<strong>Web-Based Serial Console Server<\/strong>&nbsp;using open-source tools like&nbsp;<strong>ser2net<\/strong>,&nbsp;<strong>FastAPI<\/strong>, <strong>Nginx<\/strong>. This post explores how to modernise serial access.<\/p>\n\n\n\n<p>You have plenty of cost effective USB to RS232 Serial cable available in the market, and any OLD PC with USB ports can be used for Console Server.<\/p>\n\n\n\n<p>I have built this console server for less than \u00a3200 (I used an old Cisco Router with Serial cables, which I feel is an old-fashioned way of working).<\/p>\n\n\n\n<p>Hardware :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>One OLD HP Portable Desk PC (which has 2 USB ports and 1 Ethernet)<\/li>\n\n\n\n<li>2 Qty 4 Port USB to RS232 Cable <\/li>\n<\/ol>\n\n\n\n<p>Software :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Ubuntu 24.x LTS <\/li>\n\n\n\n<li>Ser2net<\/li>\n\n\n\n<li>ttyd<\/li>\n\n\n\n<li>socat<\/li>\n\n\n\n<li>nginx &#8211; reverse proxy<\/li>\n\n\n\n<li>FastAPI (Python Virtual Environment) run Locally.<\/li>\n<\/ol>\n\n\n\n<p>Installation :<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Ubuntu<\/strong>, you can download from the Ubuntu site, install Minimal<\/li>\n\n\n\n<li>With <strong>Static IP<\/strong> (this IP and the connected Ethernet port &#8211; where OOB management network) &#8211; that is beneficial when the Core network down, you can reach console access via OOB.<\/li>\n\n\n\n<li><strong>Ser2Net<\/strong> &#8211; &nbsp;acts as the proxy between the physical serial port and a network socket. In a modern YAML configuration, we define our endpoints to allow multiple connection.<\/li>\n\n\n\n<li><strong>ttyd<\/strong>&nbsp;is a command-line tool that shares a terminal over the web.<\/li>\n\n\n\n<li><strong>socat<\/strong>&nbsp;(Short for &#8220;Socket Cat&#8221;) is a heavy-duty utility that connects two different data streams together.<\/li>\n\n\n\n<li>The Frontend: Web-Based Terminal Access The &#8220;magic&#8221; happens when we bring this into the browser. Using a Python-based backend (FastAPI), we can serve a dashboard that manages user sessions and encapsulates terminal traffic.<\/li>\n\n\n\n<li><strong>nginx <\/strong>as a reverse proxy to connect to Local Fast API server to protect the logic behind.<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<p>#sudo apt update &amp;&amp; sudo apt install python3-pip python3-venv nginx ser2net socat ttyd -y<\/p>\n\n\n\n<p>Folder for application<\/p>\n\n\n\n<p>#mkdir  terminal-backend<\/p>\n\n\n\n<p>#cd terminal-backend<\/p>\n\n\n\n<p>activate python environment<\/p>\n\n\n\n<p>#python3 -m venv venv<\/p>\n\n\n\n<p>#pip install fastapi uvicorn[standard] sqlalchemy python-multipart<\/p>\n\n\n\n<p>create a folder structure<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.<br>\u251c\u2500\u2500 main.py<br>|__ index.html<br>\u251c\u2500\u2500 add_user.py<br>\u251c\u2500\u2500 update_pass.py<br>\u251c\u2500\u2500 user.db<br>\u2514\u2500\u2500 assets\/          <br>    \u251c\u2500\u2500 fonts\/<br>    \u2502   \u251c\u2500\u2500 local-fonts.css<br>    \u2502   \u251c\u2500\u2500 inter-v12-latin-regular.woff2<br>    \u2502   \u2514\u2500\u2500 jetbrains-mono-v13-latin-regular.woff2<br>    \u2514\u2500\u2500 icons\/<br>        \u2514\u2500\u2500 bootstrap-icons.css<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">main.py content in short<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"678\" height=\"103\" src=\"https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-2.png\" alt=\"\" class=\"wp-image-2933\" srcset=\"https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-2.png 678w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-2-300x46.png 300w\" sizes=\"auto, (max-width: 678px) 100vw, 678px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">ser2net.yaml <\/h3>\n\n\n\n<p>connection: &amp;con01<br>accepter: tcp,2001<br>connector: serialdev,\/dev\/serial\/by-path\/pci-0000:00:14.0-usbv2-0:2:1.0-port0,9600n81,local<br>options:<br>kickolduser: true<br>trace-read: \/var\/log\/ser2net\/switch1.log<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">nginx site-enable &#8211; default<\/h3>\n\n\n\n<p>server {<br>listen 80 default_server;<br>server_name _;<br>return 301 https:\/\/$host$request_uri;<br>}<\/p>\n\n\n\n<p>server {<br>listen 443 ssl default_server;<br>server_name _;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Point to your newly generated SSL files\nssl_certificate \/etc\/nginx\/ssl\/nginx.crt;\nssl_certificate_key \/etc\/nginx\/ssl\/nginx.key;\n\n# Standard security protocols\nssl_protocols TLSv1.2 TLSv1.3;\nssl_ciphers HIGH:!aNULL:!MD5;\n\nlocation \/ {\n    proxy_pass http:\/\/127.0.0.1:8000; # Points to FastAPI\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;<\/code><\/pre>\n\n\n\n<h6 class=\"wp-block-heading\">CONSOLES<\/h6>\n\n\n\n<pre class=\"wp-block-code\"><code># ----------------------------\nlocation \/console1 {\n    proxy_pass http:\/\/127.0.0.1:3001;\n    proxy_buffering off;\n    proxy_request_buffering off;\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection upgrade;\n    proxy_set_header Host $host;\n\nI will map each console as service to run port redirection\n\n<strong>console1.service<\/strong>\n\n&#91;Unit]\nDescription=TTYd Console 1\nAfter=network.target ser2net.service\n\n&#91;Service]\nExecStart=\/usr\/bin\/ttyd -p 3001 -b \/console1 -W \/usr\/bin\/script -q -a -f -c \"\/usr\/bin\/socat -,raw,echo=0 tcp:127.0.0.1:2001\" \/var\/log\/ttyd\/console1.log\nRestart=always\nRestartSec=5\n\n&#91;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p>simple HTML index.html<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE html&gt;\n&lt;html&gt;\n&lt;head&gt;\n    &lt;title&gt;Console View&lt;\/title&gt;\n    &lt;style&gt;\n        body { margin: 0; background: #000; color: #00e676; font-family: monospace; }\n        .header { padding: 10px; background: #1a1d29; border-bottom: 1px solid #2d3142; }\n        iframe { width: 100%; height: calc(100vh - 50px); border: none; background: #000; }\n    &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;div class=\"header\"&gt;SYSTEM CONSOLE: CORE-SWITCH-01&lt;\/div&gt;\n    <em>&lt;!-- Point the src to your ttyd instance --&gt;<\/em>\n    &lt;iframe src=\"http:\/\/10.10.10.10:8080\" allow=\"clipboard-read; clipboard-write\"&gt;&lt;\/iframe&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"706\" height=\"585\" src=\"https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image.png\" alt=\"\" class=\"wp-image-2915\" srcset=\"https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image.png 706w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-300x249.png 300w\" sizes=\"auto, (max-width: 706px) 100vw, 706px\" \/><\/figure>\n\n\n\n<p>Web Console<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"157\" src=\"https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1-1024x157.png\" alt=\"\" class=\"wp-image-2916\" srcset=\"https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1-1024x157.png 1024w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1-300x46.png 300w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1-768x118.png 768w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1-1536x236.png 1536w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1-705x108.png 705w, https:\/\/www.balajibandi.com\/wp-content\/uploads\/2026\/04\/image-1.png 1746w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Happy Labbinggggggggggggg !<\/p>\n\n\n\n<p><br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the world of network engineering, the &#8220;out-of-band&#8221; (OOB) management link is our last line of defence. When a remote router loses its routing table or a core switch panics, SSH is the first thing to go. Historically, this meant a physical trip to the data centre or comms room with a laptop and a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,10,7],"tags":[],"class_list":["post-2914","post","type-post","status-publish","format-standard","hentry","category-ccie-sec","category-ccie-rns","category-linux"],"_links":{"self":[{"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=\/wp\/v2\/posts\/2914","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2914"}],"version-history":[{"count":18,"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=\/wp\/v2\/posts\/2914\/revisions"}],"predecessor-version":[{"id":2935,"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=\/wp\/v2\/posts\/2914\/revisions\/2935"}],"wp:attachment":[{"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2914"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2914"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.balajibandi.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2914"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}