https://fsturmat.net/blog/04202022/ home blog about contact Hosting a Public Website on MS-DOS --------------------------------------------------------------------- If you can read this, this means that my setup somehow works. If you want to read this article for entertainment purposes, feel free to proceed. But if you intend to replicate my setup, please be aware that all DOS-based webservers are prone to a variety of issues: * You might be able to create dynamic websites with QuickBASIC or something similar. This has already been done and was proven possible, but it will be less comfortable than PHP. * You will have to comply with DOS-typical file name restrictions. AWESOM~1.LOL! If you are using a static website builder like Hugo, prepare yourself for countless hours spent with renaming your assets. * Your server will be secure because it's obscure. But it's still very likely to become a target for autistic geniuses. * If your site needs to load many assets at the same time, some of those files will end up corrupted on the client-side. That's because DOS webservers can only keep track of 8- or 16-ish concurrent requests. Unless your site is really simple, you will have to cheat at some point and put some of your files somewhere else. I'm also guilty of this. In order to host a public website on MS-DOS, I have rented a cheap VPS at Hetzner Online and a domain name at IONOS. I picked both companies because I've been using their services for years, but for no other reason in particular. Any other provider will probably do just as well. Step 1: DNS Configuration --------------------------------------------------------------------- Setting up the DNS config for my website was quite easy. I simply had to obtain the IPv4 address of my VPS and create two address records. [dnsconf] The first entry will indicate a target IP address for "fsturmat.net". The second entry will indicate a target IP address for all of my subdomains, such as "test.fsturmat.net" or "myleg.fsturmat.net". Step 2: Setting up the Server --------------------------------------------------------------------- When setting up my server, I had to pick an operating system of my choice. Hetzner has a variety of Linux distributions available that can be installed with the ease of one click. For obvious reasons, they do not provide servers with a native DOS installed. And if so, I wouldn't get very far due to lacking network card drivers. This means that I'd have to set up a virtual machine running under some kind of Linux. Thus, I have chosen Debian 10 to be the host OS. root@fsturmat:~# apt-get update root@fsturmat:~# apt-get upgrade root@fsturmat:~# apt-get install qemu-system-i386 tmux Right after SSHing into my server and setting a custom root password, I did the regular APT stuff and installed QEMU along with tmux. Both applications allow me to have a DOS virtual machine running in background. QEMU also allows me to interact with this virtual machine within a plain terminal when needed. No need to install a desktop environment. Step 3: Obtaining a DOS Floppy Image --------------------------------------------------------------------- This step actually took most of my time, as I had to find a floppy image that will get me right into a command prompt instead of an installation routine. After finding a suitable image of MS-DOS 6.22, I had to dumb it down a bit and throw out some unneeded utilities, such as a CD driver. When trying to recreate this, having an EDIT.COM on that image will be quite useful. You might also want to take a look at FreeDOS, as it provides the same (dis-)comfort as MS-DOS does. Step 4: Setting up the Virtual Machine --------------------------------------------------------------------- The next step was setting up a separate user my DOS environment would start as. Running QEMU as root would be a bit of an overkill. root@fsturmat:~# adduser user I was then greeted by an input prompt to set a secure password and some other stuff with. Under some Linuxes, "user" might have been set up already. If you prefer to use a different name when following my steps, make sure to replace any other occurrences of "user" with your name of choice. As the user's home directory has now been created, it was time for me to drop my floppy image into it. I've mounted the server's home directory at my local computer using SSHFS. The image file will be referred to as "boot.img". root@fsturmat:~# cd /home/user root@fsturmat:/home/user# mkdir htdocs root@fsturmat:/home/user# chmod 777 boot.img root@fsturmat:/home/user# chmod 777 htdocs I know, there are many reasons not to use chmod 777, but it will get the job done. The folder "htdocs" will contain the HTML stuff I want the server to provide. To the virtual machine, it will appear as drive C: Instead of starting a QEMU instance manually after each reboot, I built myself a systemd service to do the dirty work for me. I've created a new file named "/etc/systemd/system/msdos.service" that contains the following: [Unit] Description=This thing starts a new tmux session with MS-DOS running inside of it. [Service] Type=forking User=user ExecStartPre=/usr/bin/tmux new-session -s msdos -d ExecStart=/usr/bin/tmux send-keys -t msdos "qemu-system-i386 -fda /home/user/boot.img -drive file=fat:rw:/home/user/htdocs -boot a -net nic,model=pcnet -net user,hostfwd=tcp::8080-:80 -curses" Enter ExecStop=/usr/bin/tmux kill-session -t msdos [Install] WantedBy=multi-user.target Just as always, make sure to mention the correct user when using a different username. When being started, this service will do many things at once: * The service will spawn a new tmux session under the name "msdos". This allows me to have a terminal session running in background that can be opened, closed and reopened without terminating the process inside of it. Sessions running under tmux can also be seen and interacted with by multiple users at the same time. It's like TeamViewer for the command line! * When the session has been created, it will be used to launch QEMU with various parameters: + Mount "/home/user/boot.img" as drive A: + Mount the entire folder "/home/user/htdocs" as drive C: (When modifying this directory as the host, the virtual machine needs to be restarted for changes to take effect. I will get back to this later.) + Do not try to boot from hard disk, use the floppy drive instead. + A virtual AMD PCNET network card will be plugged into the virtual machine... virtually, of course. (This network card is DOS-compatible.) + Set up a port-forwarding rule that allows the host machine to access the VM's port 80 under "http://localhost:8080". (You can't forward port 80 directly, as it can only be made use of as root. I will also get back to this later.) + Do not use a GUI, run as a terminal application instead. * When being stopped or restarted, the service will dispose of the tmux session allowing a new one to be created. root@fsturmat:/home/user# systemctl enable msdos.service root@fsturmat:/home/user# reboot When enabling the service, systemd will take care of starting the virtual machine after each reboot. root@fsturmat:~# systemctl restart msdos.service The command above will cause the virtual machine to restart as a whole. As I have previously mentioned: This will become important when making changes to the "htdocs" folder. Step 5: SSHing into COMMAND.COM --------------------------------------------------------------------- After rebooting, I made sure to SSH into the server as user instead of root. user@fsturmat:~$ tmux a -t msdos Attaching to the session allows me to see what is going on inside my VM: [sshlogin] Interacting with an MS-DOS command line through SSH - impressive, isn't it? As there's quite a lot of DOS-based software still in use, this might be a cheap and efficient way for an engineer to turn their client's prehistoric software into a "cloud-based" service. This may also be a good starting point for sending automated input into DOS-dependent applications. I assume that QEMU simply tries to read the VM's video memory at fixed intervals. This feature requires applications to run in plain text mode, of course. Step 5.5: Autoattaching into DOS (optional) --------------------------------------------------------------------- As I don't want to re-attach to that tmux session manually when logging in as user, I have done some changes to "/home/user/.bashrc" and added the following: if [[ ! $TERM =~ screen ]]; then tmux a -t msdos exit 0; fi When starting an SSH session as user, I will now be greeted by that DOS prompt immediately. The if-then statement is quite important, as my DOS environment runs inside the exact same shell. When missing, "tmux a -t msdos" may also be executed inside of tmux sessions causing an infinite loop of session attachments. At least that's my theory. Step 6: Installing HTTPServ --------------------------------------------------------------------- I know that there are at least three HTTP servers available for DOS: Sioux, WebServ and mTCP HTTPServ. I'm quite attracted to Sioux due to its CGI support, but it hasn't seen an update for quite a long time. Being familiar with setting up mTCP, HTTPServ was my server of choice. I simply had to create a new directory called "MTCP" on drive A: containing the following files: * PCNTPK.COM - The packet driver required by the virtual network card. I got it from Georg Potthast's website. * HTTPSERV.EXE - The webserver itself. I got it from the official page. * DHCP.EXE - Required by HTTPServ. I found it in the same directory HTTPSERV.EXE was located in. * MTCP.CFG - A simple text file containing a few settings. I had to create it myself. Getting those files onto that floppy image requires a tiny bit of creativity. You might want to mount the image under Linux directly. Optionally, you could drop those executables into "/home/user/htdocs" and move them from C: to A: using DOS. Just keep in mind that you will have to restart your VM when doing so. [mtcpcfg] The hostname isn't really important, but HTTPSERV_SERVERNAME should be set to the domain name that is being used. [autoexec] It's important to use the same packet driver interrupt in both configuration files. When rebooting the machine, the server will start on its own. root@fsturmat:~# curl localhost:8080 [request] HTTPServ should now be able to respond to any request. Obviously, I do not desire to keep my service running under port 8080. There are two ways to handle this. Step 7 / Option A: Redirecting Ports Using iptables (NOT RECOMMENDED) --------------------------------------------------------------------- You really might want to skip this part, as option B will show how I managed to get SSL working on my website. root@fsturmat:~# iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080 root@fsturmat:~# iptables -t mangle -A PREROUTING -p tcp -m tcp --dport 8080 -j MARK --set-mark 1 root@fsturmat:~# iptables -A INPUT -m mark --mark 1 -j DROP root@fsturmat:~# apt-get install iptables-persistent These iptable rules will forward port 80 to 8080 while preventing direct requests at port 8080. You might not be able to curl port 80 from localhost, but from anywhere else on the Internet. Installing iptables-persistent will help you keep those settings, as they would normally get lost after a reboot. Step 7 / Option B: Going HTTPS With Caddy (RECOMMENDED) --------------------------------------------------------------------- Caddy is a webserver made with everything in mind. It has two essential features I really wanted to make use of in this project: Reverse proxying and easy-to-use SSL. Instead of sending requests to HTTPServ directly, clients will send them to Caddy. Being a middleman, Caddy will ask HTTPServ for the requested site and send it back to the client. While HTTPServ can stick to unencrypted transport, Caddy can make use of SSL when handling traffic. root@fsturmat:~# apt install -y debian-keyring debian-archive-keyring apt-transport-https root@fsturmat:~# curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | tee /etc/apt/trusted.gpg.d/caddy-stable.asc root@fsturmat:~# curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list root@fsturmat:~# apt update root@fsturmat:~# apt install caddy The installation process may change from time to time, so please make sure that it's still up to date. After the installation was done, I have replaced "etc/caddy/Caddyfile" with the following: www.fsturmat.net { redir https://fsturmat.net{uri} } fsturmat.net { encode zstd gzip reverse_proxy localhost:8080 header -expires header Cache-Control "max-age=3600" } When restarting Caddy, it will automatically try to obtain an SSL certificate for the given domain name. root@fsturmat:~# systemctl reload caddy root@fsturmat:~# iptables -A INPUT -p tcp -s localhost --dport 8080 -j ACCEPT root@fsturmat:~# iptables -A INPUT -p tcp --dport 8080 -j DROP root@fsturmat:~# apt-get install iptables-persistent These iptable rules will prevent port 8080 from being requested externally. Installing iptables-persistent will help you keep those settings, as they would normally get lost after a reboot. Step 8: Hosting Some Content --------------------------------------------------------------------- [S:Before you start hosting your site, let me tell you: HTTPServ has a weird way of handling index files. Instead of returning "index.htm" by default, HTTPServ will return the error "403 Forbidden". You can make it behave like a normal webserver by creating a file called "HTACCESS.---" that contains the following: directoryIndex index.htm Unfortunately, this file needs to be put in each single directory of your webserver. But I think that's quite a small price you need to pay for running an MS-DOS webserver in 2022.:S] As of July 2022, this step is outdated. Since the release of mTCP 2022-07-01, HTTPServ will serve "index.htm" by default. Positive Aftermath --------------------------------------------------------------------- If there's a positively connoted version of "aftermath", please tell me. Anyway, I've been running the exact same setup for three entire months by now and I'm quite impressed by its stability. I actually expected the server to run into memory issues after exceeding some major uptime - this never turned out to happen. I've also been in contact with Michael Brutman, the mastermind behind mTCP, during the first few weeks of my DOS-based webhosting journey. The entire conversation started with a minor, rather unrelated bug report of mine, but I seized the opportunity to mention my website in passing. `There are at least two web sites using the mTCP HTTP server (https:/ /www.palmtoptube.com/ and https://fsturmat.net/). One runs on vintage hardware, while the other runs in emulated hardware on a cloud server. Both use a reverse proxy to provide HTTPS support for the mTCP HTTP server, which is a great demonstration of how to mix new and old tech. (In the early days of the web HTTP servers were often assisted by cryptographic coprocessing hardware, so this approach has precedent.) Mark Sherman has been running the 2015 version of the mTCP HTTP server in an emulator on Amazon Web Services since October 10th, 2019. That is correct, it is at 23760 hours (990 days) of runtime as of this writing. Even though the site is minimal, the endurance is amazing.' To my surprise, I found a link to my very own website in his recent release notes. So it turns out that there are two more instances of Michael's webserver in the wild, both of which are definitely worth checking out. I really enjoy that the one-man-team behind mTCP keeps on improving this great project and I really hope to see more updates coming. home blog about contact