Limited time offer: Get .COM at ₦10000 Use NGNEWCOM
India English
Kenya English
United Kingdom English
South Africa English
Nigeria English
United States English
United States Español
Indonesia English
Bangladesh English
Egypt العربية
Tanzania English
Ethiopia English
Uganda English
Congo - Kinshasa English
Ghana English
Côte d’Ivoire English
Zambia English
Cameroon English
Rwanda English
Germany Deutsch
France Français
Spain Català
Spain Español
Italy Italiano
Russia Русский
Japan English
Brazil Português
Brazil Português
Mexico Español
Philippines English
Pakistan English
Türkiye Türkçe
Vietnam English
Thailand English
South Korea English
Australia English
China 中文
Canada English
Canada Français
Somalia English
Netherlands Nederlands

Step-by-Step Guide to Deploy an ASP.NET Website on a VPS

Buy domains, business emails, hosting, VPS and more: Get Started

Cheapest Domains in Nigeria

Get your .com.ng domain now for just ₦5,500

.COM.NG for ₦5,500 | .COM for ₦10,000

You finished building your ASP.NET app. It runs fine on your laptop. Then reality sets in. “It works on my machine” is not a launch plan, and your app still has no home online.

Now come the real questions. Do you pick Linux or Windows? Which reverse proxy sits in front of your app? What happens the moment you close your SSH session and the app quietly dies?

This guide removes the guesswork. It walks you through every step, from a blank VPS to a live, secured ASP.NET site. Each stage is tested before you move to the next, so nothing is left to chance.

Prerequisites Before You Start

Before you touch a terminal, gather a few things first. This saves you from having to stop halfway through a step to find something.

  • A VPS with root or sudo access, running Ubuntu 24.04 LTS
  • A registered domain name with access to its DNS settings
  • Your ASP.NET Core project is published and ready to move to the server
  • An SSH client and basic comfort with terminal commands

Step 1: Secure and Prepare the VPS

ssh into root vps sudo apt update && sudo apt upgrade -y

A fresh VPS ships with default settings that leave it open to attack. Lock it down before you install anything else on it.

Start by updating the system so you run current packages from the start.

sudo apt update && sudo apt upgrade -y

Next, create a non-root user with sudo access. Running everything as root raises your risk if anything goes wrong later.

sudo adduser deployuser

sudo usermod -aG sudo deployuser

Switch to key-based SSH authentication instead of a password. Copy your public key to the server, then confirm you can log in with it.

ssh-copy-id deployuser@your_server_ip

Once key-based login works, disable root login and password authentication in /etc/ssh/sshd_config. Set PermitRootLogin no and PasswordAuthentication no, then restart SSH.

sudo systemctl restart ssh

Finally, set up a firewall with UFW. Allow only the ports your app actually needs.

sudo ufw allow OpenSSH

sudo ufw allow 80

sudo ufw allow 443

sudo ufw enable

Step 2: Install the .NET Runtime on the VPS

sudo dpkg -i packages-microsoft-prod.deb

sudo apt update

sudo apt install -y aspnetcore-runtime-10.0

Your ASP.NET Core app requires the corresponding runtime to be installed on the server. Add the Microsoft package repository first, then install the runtime.

wget https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb

sudo apt update

sudo apt install -y aspnetcore-runtime-10.0

.NET 10 is the current long-term support release, so it makes sense as your default choice for a new production app. If your project still targets .NET 8, install that runtime version instead.

Confirm the install worked with this command.

dotnet --info

Check that the runtime version on the server matches the target framework in your project file. A mismatch here causes startup failures later, so catch it now.

Step 3: Transfer and Publish the Application

asp.net publish application on vps

Build your app for production on your local machine first. This trims unnecessary files and optimizes the output for deployment.

dotnet publish -c Release -o ./publish

Copy the published folder to your VPS with scp or rsync. Either tool works, but rsync handles repeat transfers more efficiently.

scp -r ./publish deployuser@your_server_ip:/var/www/yourapp

Set the correct ownership on the app folder once the files land on the server.

sudo chown -R deployuser:deployuser /var/www/yourapp

Before moving on, run the app manually to confirm it starts without errors.

cd /var/www/yourapp

dotnet YourApp.dll

If you see the app listening on a local port, you are ready for the next step. Stop the process with Ctrl+C once you confirm it.

Step 4: Keep the App Running with systemd

how to start your app using systemd on vps

Running the app manually only works while your terminal session stays open. systemd solves this by managing the app as a background service.

Create a new unit file.

sudo nano /etc/systemd/system/yourapp.service

Add the following configuration, then adjust the paths to match your setup.

[Unit]

Description=YourApp ASP.NET Core Application

After=network.target

[Service]

WorkingDirectory=/var/www/yourapp

ExecStart=/usr/bin/dotnet /var/www/yourapp/YourApp.dll

Restart=always

RestartSec=10

KillSignal=SIGINT

SyslogIdentifier=yourapp

User=deployuser

Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]

WantedBy=multi-user.target

Enable and start the service.

sudo systemctl enable yourapp.service

sudo systemctl start yourapp.service

Close your SSH session, then reconnect and check the service status. If it still shows active, your app now survives disconnects on its own.

sudo systemctl status yourapp.service

For a closer look at what the app is doing, run journalctl to pull its logs.

sudo journalctl -u yourapp.service -f

Step 5: Configure Nginx as a Reverse Proxy

update and install nginx web server on vps

Kestrel, the web server built into ASP.NET Core, was never meant to face the public internet directly. Nginx sits in front of it and handles that job instead.

Install Nginx first.

sudo apt install -y nginx

Create a server block for your domain.

sudo nano /etc/nginx/sites-available/yourapp

Add this configuration, then replace it with your own domain name.

server {

    listen 80;

    server_name yourdomain.com www.yourdomain.com;

    location / {

        proxy_pass http://localhost:5000;

        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;

        proxy_set_header Connection keep-alive;

        proxy_set_header Host $host;

        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;

    }

}

Enable the site, test the config, then reload Nginx.

sudo ln -s /etc/nginx/sites-available/yourapp /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl reload nginx

Visit your domain in a browser now. If your app loads over plain HTTP, move on to SSL. If not, check the troubleshooting section below before continuing.

Step 6: Point Your Domain and Secure the Site with SSL

Your DNS records need to point at your VPS before certificates or public access will work correctly. Log in to your domain registrar or DNS provider and add an A record.

Point the A record for your domain at your VPS IP address. Propagation can take anywhere from a few minutes to a few hours.

Once the domain resolves to your server, install Certbot and request a certificate.

sudo apt install -y certbot python3-certbot-nginx

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot automatically updates your Nginx config and sets up HTTP-to-HTTPS redirection for you. Follow the prompts to finish the process.

Auto-renewal is enabled by default through a systemd timer. Confirm it works with a dry run.

sudo certbot renew --dry-run

Your site now runs on HTTPS. Nginx handles the certificate at the edge, so Kestrel never has to deal with SSL directly.

Step 7: Redeploy Updates Without Downtime

Your app will need updates after launch, and users should not notice when that happens. Build a repeatable process for this before your first real deploy.

Publish your updated build locally, then copy it to a staging path on the server instead of overwriting the live folder directly.

scp -r ./publish deployuser@your_server_ip:/var/www/yourapp-new

Swap the folders once the new build lands, then restart the service.

sudo mv /var/www/yourapp /var/www/yourapp-old

sudo mv /var/www/yourapp-new /var/www/yourapp

sudo systemctl restart yourapp.service

Check that the site loads correctly and the service status shows active. If something breaks, swap the old folder back in and restart the service.

sudo systemctl status yourapp.service

Keep the previous build folder around for a while after each deploy. A quick rollback beats a long debugging session under pressure.

Why Deploy ASP.NET Core on a Linux VPS

A Linux VPS costs less than a Windows VPS for most small and mid-size ASP.NET Core apps.

Windows licensing adds a monthly fee that Linux servers avoid entirely. For a lean production app, that saving adds up fast.

Modern ASP.NET Core runs across platforms. It no longer needs Windows or IIS to serve traffic.

The legacy .NET Framework still ties you to Windows, but ASP.NET Core does not.

A VPS gives you full control over the server. You choose the operating system, the runtime version, and every security setting. Shared hosting never gives you that level of access.

For a small production app, start with 1-2 vCPUs and 2 GB of RAM. Scale up once traffic exceeds what your current plan can handle.

Troubleshooting Common Deployment Issues

Deployments rarely go perfectly on the first try, and that is normal. Here is how to work through the failures you are most likely to hit.

Fixing a 502 Bad Gateway Error

A 502 error means Nginx received no valid response from Kestrel. It does not mean your code has a bug. The cause usually sits between the proxy and the app, not inside your logic.

  • Confirm Kestrel is actually running and listening on the port your app expects.
  • Check that the systemd service is active, since a stopped Kestrel process is the most common cause of a 502.
  • Match the port in your Nginx proxy_pass line against the port Kestrel binds to in your app settings.
  • Increase the Nginx proxy timeouts if the app responds slowly, rather than failing outright.

Resolving Port Binding and Address Already in Use Errors

This error shows up when another process already holds the port your app wants. Find that process before you restart anything.

sudo ss -tunlp | grep 5000

The output shows the process name and PID using that port. Stop that process, or reassign your app to a different port, then try again.

This error often shows up right after a redeploy when an old Kestrel process from a previous version hasn’t fully shut down.

Fixing Permission Denied Errors

Permission errors usually indicate a mismatch between file ownership and the user that your systemd service runs as. Check ownership of the app folder first.

sudo chown -R deployuser:deployuser /var/www/yourapp

sudo chmod -R 755 /var/www/yourapp

Watch for this error if you try to bind Kestrel directly to port 80 or 443. Linux blocks non-root processes from binding to ports below 1024 by default.

Keep Kestrel on a high port like 5000, then let Nginx handle 80 and 443 instead. This sidesteps the whole problem.

Diagnosing a systemd Service That Fails to Start

When a service refuses to start, check its status first. The short output usually names the exact reason.

sudo systemctl status yourapp.service

Pull the fuller logs next for the actual error the app threw on startup.

sudo journalctl -u yourapp.service -n 50 --no-pager

Check the ExecStart path in your unit file for typos, and confirm the .dll filename matches your latest published build. A stale filename after a redeploy is an easy mistake to make.

Fixing HTTPS and Certificate Binding Errors

Certificate errors on Kestrel almost always trace back to a missing or expired certificate reference, not a code problem. The fix is usually simpler than it looks.

Let Nginx terminate SSL and pass plain HTTP through to Kestrel internally. This setup avoids most certificate configuration on the Kestrel side entirely.

If HTTPS suddenly stops working, run the Certbot renewal command manually first.

sudo certbot renew

Check the Nginx error log if the issue persists. SSL failures usually surface there before anywhere else.

sudo tail -f /var/log/nginx/error.log

FAQs

Can ASP.NET run on a Linux VPS?

What is the difference between Kestrel and IIS for ASP.NET hosting?

How much RAM and CPU do you need for an ASP.NET Core application?

Do you need Nginx or Apache in front of Kestrel?

Deploy Your ASP.NET Apps Today

You now have a full path from a blank VPS to a live, secure ASP.NET site, with testing at every stage along the way.

As your app grows beyond the resource limits covered in Step 1, scale up to a larger VPS plan built for production traffic.

That upgrade is the natural next step once your current setup starts to feel tight.

Elias N
Author

Elias N

SEO Expert Nairobi, KEN

SEO nerd by trade. Obsessing over keywords, content, and why Google does what it does.

View All Posts