Whether you are shipping a SaaS product, an internal dashboard, or a public-facing API, getting your .NET application onto a production server reliably is a critical step. This guide walks you through deploying an ASP.NET Core application to a DigitalOcean Droplet using DeployHQ for automated, repeatable deployments.
We will cover server provisioning, application configuration, NGINX reverse proxy setup, SSL certificates, firewall rules, and how to wire everything together with DeployHQ so that every git push triggers a production deployment automatically.
flowchart TD
A[Developer pushes code to Git repository] --> B[DeployHQ detects changes via webhook]
B --> C[DeployHQ runs build commands]
C --> D["dotnet restore && dotnet publish"]
D --> E[DeployHQ uploads artifacts via SSH]
E --> F[Post-deploy script restarts the service]
F --> G[Systemd manages the .NET process]
G --> H[NGINX reverse proxies traffic to Kestrel]
H --> I[Application is live]
Requirements
Before starting, make sure you have the following:
- DigitalOcean account with a Droplet running Ubuntu 24.04 LTS (or 22.04 LTS)
- DeployHQ account — sign up for a free trial if you do not have one
- .NET project in a Git repository (this guide uses .NET 10, the current LTS release as of 2026, though .NET 8 is also still supported until November 2026)
- SSH key pair for secure server access — see our guide on creating SSH keys from the command line if you need help generating one
- Domain name (optional, but required for SSL)
1. Provision and Prepare the DigitalOcean Droplet
Start by creating a Droplet in the DigitalOcean dashboard. A Basic Droplet with 1 GB RAM and 1 vCPU is sufficient for small applications, though you may want to scale up for heavier workloads. If you have not used DeployHQ with DigitalOcean before, our article on using DeployHQ with DigitalOcean covers the integration in more detail.
Once your Droplet is running, SSH into it and install the .NET SDK and runtime. We are using .NET 10 here, but you can substitute 10.0 with 8.0 if your project targets .NET 8:
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y dotnet-sdk-10.0 aspnetcore-runtime-10.0
dotnet --version
If the packages are not available in the default Ubuntu repositories, follow the official Microsoft instructions to add the Microsoft package feed first.
Install and Configure the Firewall
Securing your server with a firewall is an essential step. We will use ufw (Uncomplicated Firewall) to allow only the traffic we need:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
This allows SSH access (port 22) and both HTTP (port 80) and HTTPS (port 443) traffic through NGINX. All other ports are blocked by default.
2. Configure Your .NET Application for Production
Before deploying, there are a few configuration changes to make in your project. These ensure the application runs correctly behind a reverse proxy on a Linux server.
Set Kestrel URLs via appsettings or Environment Variables
In development, you might use launchSettings.json to configure URLs and environment variables. However, launchSettings.json is a development-only file and is ignored by dotnet publish. For production, configure the Kestrel listening address using appsettings.Production.json or environment variables.
Option A: appsettings.Production.json
Create or edit appsettings.Production.json in your project root:
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5000"
}
}
}
}
Option B: Environment variable (set in the systemd service file later)
Environment=ASPNETCORE_URLS=http://localhost:5000
Either approach ensures Kestrel listens on port 5000 in production, which is what our NGINX reverse proxy will forward traffic to.
Remove HTTPS Redirection Middleware
Because NGINX will handle SSL termination, remove or comment out the UseHttpsRedirection() middleware in your Program.cs file. If left in, it can cause redirect loops since Kestrel only receives plain HTTP traffic from NGINX.
While you are editing Program.cs, add the UseForwardedHeaders() middleware so that your application sees the original client IP address and protocol instead of the NGINX proxy address:
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
// Remove or comment out: app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
app.Run();
3. Create the Systemd Service File
On your DigitalOcean Droplet, create a systemd service file to manage the .NET application process. This ensures the application starts on boot, restarts automatically on failure, and can be controlled with standard systemctl commands.
Create the directory where the published application will live:
sudo mkdir -p /var/www/your-app
sudo chown www-data:www-data /var/www/your-app
Then create the service file:
sudo nano /etc/systemd/system/your-app.service
Add the following configuration, replacing your-app with your actual application name:
[Unit]
Description=Your .NET Application
After=network.target
[Service]
WorkingDirectory=/var/www/your-app
ExecStart=/usr/bin/dotnet /var/www/your-app/your-app.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=your-app
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://localhost:5000
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
Key settings explained:
- Restart=always and RestartSec=10: If the process crashes, systemd waits 10 seconds and restarts it automatically.
- User=www-data: Runs the application under a low-privilege user for security.
- KillSignal=SIGINT: Allows the .NET runtime to perform a graceful shutdown.
- After=network.target: Ensures networking is available before starting the application.
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable your-app
sudo systemctl start your-app
sudo systemctl status your-app
4. Configure NGINX as a Reverse Proxy
NGINX sits in front of Kestrel and handles SSL termination, static file caching, and load balancing. Install it if it is not already present:
sudo apt-get install nginx
Create a new site configuration:
sudo nano /etc/nginx/sites-available/your-app
Add the following configuration:
server {
listen 80;
server_name your-domain.com www.your-domain.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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
The X-Forwarded-For and X-Forwarded-Proto headers are what the UseForwardedHeaders() middleware reads to determine the real client IP and protocol.
Enable the site and test the configuration:
sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
If nginx -t reports any errors, fix them before reloading. A common mistake is forgetting the semicolons at the end of directives.
5. Configure DeployHQ
With the server ready, it is time to connect DeployHQ to your repository and server.
Connect Your Repository
- Log in to your DeployHQ dashboard and create a new project.
- Connect your Git repository. DeployHQ supports GitHub, GitLab, Bitbucket, and other providers. If you are using GitHub, see our guide on deploying from GitHub for the quickest setup path.
- Select the branch you want to deploy from (typically
mainorproduction).
Add Your Server
- Navigate to Servers and click New Server.
- Choose the SSH/SFTP protocol.
- Enter your Droplet's IP address, SSH port (default 22), and authentication credentials.
- Set the deployment path to
/var/www/your-app. - Use SSH key authentication for security. DeployHQ can generate a key pair for you, or you can use your own.
Configure Build Commands
DeployHQ can run build commands before uploading files, which is ideal for compiling .NET projects. To learn more about what build pipelines can do, read our guide on build pipelines in DeployHQ.
Set the following build commands in your server configuration:
dotnet restore
dotnet publish --configuration Release --output ./publish
This restores NuGet packages and publishes a Release build to the ./publish directory. Configure DeployHQ to deploy from the publish subfolder so only compiled output reaches your server.
Configure Post-Deploy Commands
After files are uploaded, DeployHQ needs to restart the application service. Add this as a SSH command that runs after deployment:
sudo systemctl restart your-app
For the www-data or deploy user to run this command without a password prompt, add a sudoers rule:
sudo visudo -f /etc/sudoers.d/deploy
Add:
deploy-user ALL=(ALL) NOPASSWD: /bin/systemctl restart your-app
Replace deploy-user with the actual SSH user DeployHQ connects as.
6. Enable Zero-Downtime Deployments
Simply restarting the service with systemctl restart causes a brief interruption while the new process starts. For true zero-downtime deployments, DeployHQ offers a zero-downtime deployment feature that keeps the old version running until the new one is ready.
To learn the concepts behind zero-downtime strategies in depth, read our article on zero-downtime deployments and keeping your application running smoothly.
To enable this in your DeployHQ server configuration:
- Go to your server settings in DeployHQ.
- Enable the Zero Downtime option under the deployment strategy section.
- DeployHQ will maintain multiple release directories and symlink the current one, so the old version keeps serving requests until the new one is fully deployed.
Update your systemd service file to point to the symlinked path:
WorkingDirectory=/var/www/your-app/current
ExecStart=/usr/bin/dotnet /var/www/your-app/current/your-app.dll
7. Set Up SSL with Let's Encrypt
SSL is no longer optional for production applications. Install Certbot and generate a free certificate from Let's Encrypt:
sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
Certbot automatically modifies your NGINX configuration to redirect HTTP to HTTPS and sets up automatic certificate renewal. You can verify the renewal process with:
sudo certbot renew --dry-run
8. Enable Automatic Deployments
Manual deployments are fine for initial setup, but for ongoing development you want deployments triggered automatically when you push code.
- In DeployHQ, navigate to your project's Automatic Deployments settings.
- Enable automatic deployments for your target branch.
- Copy the webhook URL provided by DeployHQ.
- Add this webhook to your Git provider (GitHub, GitLab, or Bitbucket) in the repository settings under Webhooks.
Now every push to your configured branch will trigger a deployment automatically. For a broader look at deployment best practices, our ultimate deployment checklist covers everything you should verify before and after going live.
9. Monitor Your Deployment
After deploying, verify that everything is running correctly.
Check application status:
sudo systemctl status your-app
journalctl -u your-app -f --no-pager -n 50
The journalctl command tails the application logs in real time, which is invaluable for spotting startup errors.
Check NGINX status:
sudo nginx -t
sudo systemctl status nginx
Check deployment logs in DeployHQ:
The DeployHQ dashboard shows a full log of each deployment including build output, file transfers, and post-deploy command results. If a deployment fails, the log is the first place to look.
Working with Database Migrations
If your .NET application uses Entity Framework Core or another migration tool, you need to run migrations as part of the deployment process. We have two dedicated guides for this:
- Automating database migrations in .NET with DeployHQ and DbUp covers the DbUp approach
- Code-first database deployments with .NET Core and Entity Framework covers the EF Core approach
Both guides show how to integrate migrations into your DeployHQ build pipeline so they run automatically on every deployment.
Managing Multiple Environments
For real-world projects, you typically need separate staging and production environments. DeployHQ makes this straightforward by allowing you to configure multiple servers per project, each linked to a different branch. Our guide on managing multiple environments with DeployHQ walks through this setup in detail.
Troubleshooting
The application starts but is not accessible in the browser
- Verify the service is running:
sudo systemctl status your-app - Check the firewall allows HTTP/HTTPS:
sudo ufw status - Confirm NGINX is proxying to the correct port:
curl -v http://localhost:5000from the Droplet - Look at NGINX error logs:
sudo tail -f /var/log/nginx/error.log
502 Bad Gateway from NGINX
This means NGINX cannot connect to Kestrel. Common causes:
- The .NET application is not running. Check
systemctl status your-app. - The application is listening on a different port. Verify the
ASPNETCORE_URLSenvironment variable matches theproxy_passURL in NGINX. - The application crashed on startup. Check
journalctl -u your-app -n 100for errors.
Permission denied errors during deployment
- Ensure the deployment directory is owned by the correct user:
sudo chown -R www-data:www-data /var/www/your-app - Verify the DeployHQ SSH user has permission to write to the deployment directory.
- Check the sudoers file allows the deploy user to restart the service without a password.
SSL certificate renewal fails
- Run
sudo certbot renew --dry-runto test renewal. - Ensure port 80 is open in the firewall (
sudo ufw allow 'Nginx Full'). - Check that Certbot's systemd timer is enabled:
sudo systemctl status certbot.timer
dotnet publish fails during build
- Verify the correct .NET SDK version is installed on the build environment.
- Check for NuGet package restore errors in the DeployHQ build log.
- Ensure your
.csprojtargets a framework version available on the server runtime.
FAQ
Can I deploy a .NET Framework (non-Core) application with this setup?
No. This guide is for ASP.NET Core (.NET 6+) applications that run cross-platform on Linux. Classic .NET Framework applications require Windows hosting with IIS.
Do I need to install the full .NET SDK on the production server?
If DeployHQ handles the build step (compiling via dotnet publish), you only need the ASP.NET Core runtime on the server, not the full SDK. However, if you want to build on the server itself, you will need the SDK. We recommend building in DeployHQ and deploying only the published output.
What .NET version should I use?
As of 2026, .NET 10 is the current Long-Term Support (LTS) release and is recommended for new projects. .NET 8 is still supported until November 2026, so existing .NET 8 applications do not need an immediate upgrade, but plan your migration soon.
Can I use Docker instead of running Kestrel directly?
Yes. You can containerize your .NET application and deploy the Docker image to your Droplet. The systemd service would then manage the Docker container instead of the dotnet process directly. The NGINX reverse proxy configuration remains the same.
How do I handle environment-specific configuration (connection strings, API keys)?
Use environment variables set in the systemd service file or in a .env file loaded by systemd. Never commit secrets to your Git repository. You can also use the appsettings.Production.json file for non-sensitive production configuration, but keep secrets in environment variables.
Does DeployHQ support deploying to multiple DigitalOcean Droplets?
Yes. You can add multiple servers to a single DeployHQ project and deploy to all of them simultaneously. This is useful for load-balanced setups. Check the DeployHQ pricing page for server limits on each plan.
Next Steps
You now have a fully automated deployment pipeline for your .NET application on DigitalOcean, with NGINX as a reverse proxy, SSL encryption, firewall protection, and automatic deployments triggered by Git pushes.
Ready to automate your .NET deployments? Sign up for DeployHQ and have your first deployment running in minutes.
If you have questions or need help with your deployment setup, reach out to us at support@deployhq.com or find us on X/Twitter @deployhq.