My Short, Sloppy Guide for Migrating a Tiny Web Host From an Azure VM to a Linux VPS

Author

Brandon Bruno

Published

Tags

After more than 10 years running my websites and webapps on an Azure VM, I've migrated to a Linux VPS for a couple of key reasons:

  • Cost savings: My Azure VM was initially a free option in 2016, then a cheap option, and nowadays a not-so-cheap option; cost predictability is important to me, and a Linux VPS provides that (so far).

  • Simplicity: Azure infrastructure is more than just a VM: it's networking interfaces, storage accounts, network security groups, IP addresses, and more. While the fine-grain control is great, Microsoft is constantly upgrading services individually, creating constant work for me.

  • .NET rocks: Moving to Linux gave me a chance to migrating my .NET Framework apps to .NET 8. In doing so I've learned a ton about cross-platform compatibility in the .NET ecosystem.

Choosing a VPS Provider

This was a rollercoaster. Reducing hosting costs was my primary goal in choosing a VPS host, and any Linux VPS ($5-10/month) was going to beat a Windows Azure VM ($90/month) anyway - so what else mattered?

I also considered other key factors: DDos protection, configurable firewall, support options, minimum specs, US-based location, and more.

What I wasn't expecting: just how difficult it would be to get a US-based data center. Supposedly the AI crunch in 2026 has been causing a major shortage of cheap servers, so it was either host in Europe or wait for capacity.

In the end, I found an aggressively-priced VPS with much better specs than my Azure VM could offer in a US East data center. Huzzah!

The Setup

Moving from an IIS-based Windows Server environment to Linux is shockingly easy. I probably could have containerized my applications, but that's a future endeavour.

My new server is essentially:

  • Debian
  • nginx for hosting static sites and as a reverse proxy to my .NET applications
    • nginx also terminates SSL connections, so all SSL & redirect logic is handled in nginx configs
  • .NET web applications running Kestrel
  • Certbot for managing/renewing SSL certs

There's no database layer to consider - all my .NET apps now run SQLite, which drastically simplifies deployment, ongoing management, and security.

The Process

This isn't a detailed step-by-step guide for anyone to follow - it's simply the high-level steps I took to migrate everything from a Windows to Linux. This assumes all my applications are Linux compatible (which was a separate multi-month project itself).

Step 1: provision a Linux server & install software

Step 2: configure directories

  • website, application, and logging directories should be set up across the usual spots in Linux (/var, /opt, etc.)

Step 3: copy websites & applications to server

  • remoting with rsync makes this simple: copy all static and .NET websites to server in a repeatable way
  • this ends up being my (super-simple) deployment pipeline too:
    • an rsync script copies artifacts to the server
    • another rsync script copies artifacts to production directories on the server

Step 4: configure nginx for static websites

Static websites are simple to serve with nginx; just point a config to the right folders:

  • create nginx .conf files for each static site under /etc/nginx/sites-available
  • create symlink to each under /etc/nginx/sites-enabled
  • test & reload nginx service as needed

Step 5: configure .NET apps & nginx reverse proxy

My .NET applications and websites run on their built-in Kestrel server and are accessed via a reverse proxy with nginx:

  • build each application for Linux (build param: --runtime linux-x64 )
  • deploy to server
  • copy configuration & databases from old production server
  • set folder & file permissions for www-data nginx user (chmod and chown key directories)
  • create nginx .conf files for each .NET website under /etc/nginx/sites-available
    • this is a reverse proxy situation; SSL will also terminate here, so the config is different than static sites (but Certbot handles that extra configuration)
  • create symlink to each under /etc/nginx/sites-enabled
  • configure each website to start on boot

Step 6: set up automatic SSL installation & renewal

This was the easiest part of the entire migration. Certbot is just a couple of commands to get every website running with valid Let's Encrypt certificates.

  • Install and configure Certbot to handle automatic SSL certification renewal

And with those basic, high-level steps, I was up and running on a Linux server. I tested this full migration on a local VM and was able to get through it in just a couple of weeks. With a well-documented and vetted process, the final production migration took just a couple of days.

And my wallet feels better for it.