🌏 閱讀中文版本
This article provides a comprehensive guide on how to compile, publish, and run a .NET web application on Ubuntu 22.04.4 LTS using .NET SDK 6.0+, and manage it with systemd services.
Why Deploy .NET Applications on Ubuntu?
1. Cross-Platform Advantages
.NET Core and .NET 5+ provide full cross-platform support, allowing developers to:
- Reduce Licensing Costs: Use free Linux servers instead of Windows Server
- Improve Performance: .NET applications on Linux often have better memory management and execution efficiency
- Container-Friendly: Easier integration with Docker, Kubernetes, and other modern deployment tools
2. Ubuntu’s Advantages
- Long-Term Support (LTS): Ubuntu 22.04 LTS provides security updates until 2027
- Rich Package Repository: apt package management provides complete dependency management
- Community Support: Extensive documentation and community resources
3. Production Environment Considerations
- Systemd Management: Auto-restart, log management, boot startup
- Reverse Proxy Integration: Works with Nginx, Apache for HTTPS, load balancing
- Monitoring & Logging: Integrates with journalctl, Prometheus, Grafana, and other tools
Web Server Architecture Explained
.NET Application Web Server Architecture
Important Concept: .NET applications use Kestrel as the built-in cross-platform web server, not IIS, and not Nginx. Here’s an explanation of each web server’s role:
Kestrel vs IIS vs Nginx Comparison
| Web Server | Role | Platform | Description |
|---|---|---|---|
| Kestrel | Application Server | Cross-platform (Windows, Linux, macOS) |
Built-in high-performance HTTP server in .NET Directly runs .NET applications |
| IIS | Reverse Proxy + App Server | Windows Server only | Windows-specific web server Can act as reverse proxy for Kestrel on Windows |
| Nginx | Reverse Proxy | Cross-platform (Primarily Linux) |
Does not directly run .NET apps Forwards requests to Kestrel |
| Apache | Reverse Proxy | Cross-platform | Does not directly run .NET apps Forwards requests to Kestrel |
Deployment Architecture Diagram
Typical deployment architecture on Ubuntu:
Client Request (HTTPS:443)
↓
Nginx (Reverse Proxy)
├─ SSL/TLS termination
├─ Static file serving (optional)
├─ Compression, caching
└─ Load balancing (multiple Kestrel instances)
↓
Kestrel (HTTP:8080, 8081, 8082...)
└─ Runs .NET application
Why Use Nginx as Reverse Proxy?
While Kestrel can handle HTTP requests directly, it’s recommended to use Nginx as a frontend reverse proxy in production for several reasons:
- SSL/TLS Termination: Nginx handles HTTPS encryption/decryption, reducing Kestrel’s workload
- Static File Serving: Nginx serves static files (images, CSS, JS) more efficiently
- Load Balancing: Distribute traffic across multiple Kestrel instances
- Security Protection:
- Hide internal application structure
- Prevent direct attacks on Kestrel
- Implement rate limiting and DDoS protection
- Advanced Features:
- Request/response compression (gzip, brotli)
- HTTP/2 support
- Caching strategies
- Logging and monitoring
Windows vs Linux Deployment Differences
| Platform | App Server | Reverse Proxy | Management |
|---|---|---|---|
| Windows Server | Kestrel | IIS (recommended) or Nginx |
Windows Service or IIS Manager |
| Ubuntu/Linux | Kestrel | Nginx (recommended) or Apache |
systemd |
This Article’s Focus: On Ubuntu, we use Kestrel (built into .NET) + Nginx (reverse proxy) + systemd (service management).
Environment Requirements
Test Environment:
- OS: Ubuntu 22.04.4 LTS
- .NET SDK: 6.0.128 (or newer)
- User Permissions: sudo access required
Installing .NET SDK:
# Add Microsoft package signing key
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
# Install .NET SDK
sudo apt-get update
sudo apt-get install -y dotnet-sdk-6.0
# Verify installation
dotnet --version
Step 1: Navigate to Project Directory
Navigate to your project directory and ensure it contains a .sln or .csproj file:
cd /path/to/your/project
# Confirm project files exist
ls -la *.sln *.csproj
Step 2: Restore NuGet Packages
Restore all required NuGet packages for the project:
dotnet restore
# Example output:
# Determining projects to restore...
# Restored /path/to/project.csproj (in 2.3 sec).
Common Issues: If encountering NuGet source errors, clear the cache:
dotnet nuget locals all --clear
dotnet restore --force
Step 3: Build the Project
Build the project to ensure all source code compiles without errors:
dotnet build
# Or specify Release configuration
dotnet build -c Release
Successful Build Output:
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:15.23
Step 4: Publish the Project
Package the project to a specified output directory using Release configuration for optimal performance:
# Basic publish
dotnet publish -c Release -o /path/to/output/folder
# Self-contained publish (includes .NET Runtime)
dotnet publish -c Release -r linux-x64 --self-contained true -o /path/to/output/folder
# Single-file publish (.NET 6+)
dotnet publish -c Release -r linux-x64 --self-contained true \
-p:PublishSingleFile=true -o /path/to/output/folder
Publish Options Explained:
| Parameter | Description | Use Case |
|---|---|---|
| -c Release | Compile with Release configuration | Required for production |
| -r linux-x64 | Specify target runtime | Cross-platform publishing |
| –self-contained true | Include .NET Runtime | Server without .NET SDK |
| PublishSingleFile=true | Single executable | Simplified deployment |
Step 5: Backup Published Files
Create a backup mechanism for rollback capability:
# Create backup directory
mkdir -p ~/backupweb
# Archive publish directory with timestamp
cd ~
tar -czvf backupweb/publish_$(date +%Y%m%d_%H%M%S).tar.gz /path/to/output/folder
# Verify backup
ls -lh ~/backupweb/
Automated Backup Script:
#!/bin/bash
# backup_dotnet_app.sh
BACKUP_DIR=~/backupweb
APP_DIR=/path/to/output/folder
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
tar -czvf $BACKUP_DIR/publish_$TIMESTAMP.tar.gz $APP_DIR
# Keep backups from last 7 days only
find $BACKUP_DIR -name "publish_*.tar.gz" -mtime +7 -delete
echo "✅ Backup completed: publish_$TIMESTAMP.tar.gz"
Step 6: Run Application Manually (Testing)
Navigate to the publish directory and start the application for testing:
cd /path/to/output/folder
# Start application (Kestrel listens on all IPs, port 8080)
dotnet YourProjectName.dll --urls "http://0.0.0.0:8080"
Replace YourProjectName.dll with your actual DLL filename, for example:
dotnet MyProject.APIService.dll --urls "http://0.0.0.0:8080"
You’ll see Kestrel startup messages:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://0.0.0.0:8080
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
Test the Application:
# Test from another terminal
curl http://localhost:8080
# Or test specific endpoint
curl http://localhost:8080/api/health
Step 7: Manage with Systemd
Create Systemd Service File
Create a service file to manage the application lifecycle:
sudo nano /etc/systemd/system/myproject-api.service
Service File Content:
[Unit]
Description=MyProject API Service
After=network.target
[Service]
WorkingDirectory=/path/to/output/folder
ExecStart=/usr/bin/dotnet /path/to/output/folder/MyProject.APIService.dll --urls=http://0.0.0.0:8080
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-myproject
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
Service File Parameters Explained:
| Parameter | Description |
|---|---|
| After=network.target | Start after network service is ready |
| ExecStart | Execute dotnet command to start Kestrel web server |
| Restart=always | Auto-restart on abnormal termination |
| RestartSec=10 | 10-second interval before restart |
| KillSignal=SIGINT | Use Ctrl+C signal for graceful shutdown |
| User=www-data | Run as www-data user (security consideration) |
| ASPNETCORE_ENVIRONMENT | Set to Production environment |
Start and Enable Service
# Reload systemd configuration
sudo systemctl daemon-reload
# Start service
sudo systemctl start myproject-api
# Enable auto-start on boot
sudo systemctl enable myproject-api
# Check service status
sudo systemctl status myproject-api
Successful Startup Output:
● myproject-api.service - MyProject API Service
Loaded: loaded (/etc/systemd/system/myproject-api.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2024-06-22 10:00:00 UTC; 5s ago
Main PID: 12345 (dotnet)
Tasks: 15 (limit: 4915)
Memory: 45.2M
CPU: 1.234s
CGroup: /system.slice/myproject-api.service
└─12345 /usr/bin/dotnet /path/to/output/folder/MyProject.APIService.dll
Step 8: Troubleshooting Common Issues
1. Check Application Logs
Use journalctl to view application logs:
# Monitor logs in real-time
sudo journalctl -u myproject-api.service -f
# View last 100 lines
sudo journalctl -u myproject-api.service -n 100
# View today's logs
sudo journalctl -u myproject-api.service --since today
# View specific time range
sudo journalctl -u myproject-api.service --since "2024-06-22 10:00:00" --until "2024-06-22 11:00:00"
2. Check if Port is Listening
# Using netstat
sudo netstat -tuln | grep 8080
# Using ss (newer tool)
sudo ss -tuln | grep 8080
# Using lsof
sudo lsof -i :8080
Expected Output:
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
3. Check Firewall Configuration
Ensure firewall allows traffic on port 8080:
# Check ufw status
sudo ufw status
# Allow port 8080
sudo ufw allow 8080/tcp
# Or allow from specific IP range
sudo ufw allow from 192.168.1.0/24 to any port 8080
# Reload firewall
sudo ufw reload
4. Check Permission Issues
# Check file ownership
ls -la /path/to/output/folder
# Change owner to www-data
sudo chown -R www-data:www-data /path/to/output/folder
# Ensure execute permission
sudo chmod +x /path/to/output/folder/MyProject.APIService.dll
5. Check Application Configuration
Verify appsettings.json and environment variables are correctly configured:
# View environment variables
sudo systemctl show myproject-api -p Environment
# Test configuration file
cat /path/to/output/folder/appsettings.Production.json
Common Questions and Answers (FAQ)
Q1: How to Update a Deployed Application?
A: Follow these steps for zero-downtime updates:
# 1. Stop service
sudo systemctl stop myproject-api
# 2. Backup current version
tar -czvf ~/backupweb/backup_before_update_$(date +%Y%m%d).tar.gz /path/to/output/folder
# 3. Publish new version (overwrite old files)
dotnet publish -c Release -o /path/to/output/folder
# 4. Restart service
sudo systemctl start myproject-api
# 5. Check status
sudo systemctl status myproject-api
Q2: How to Configure Reverse Proxy (Nginx)?
A: Nginx acts as a reverse proxy, forwarding HTTPS requests to Kestrel. Install and configure Nginx:
# Install Nginx
sudo apt-get install nginx
# Create site configuration
sudo nano /etc/nginx/sites-available/myproject
Nginx Configuration Example (forwarding requests to Kestrel):
server {
listen 80;
server_name example.com;
location / {
# Forward requests to Kestrel (localhost:8080)
proxy_pass http://localhost:8080;
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;
}
}
Explanation:
proxy_pass http://localhost:8080: Forwards requests to Kestrel running locally- Nginx listens on port 80 (HTTP) or 443 (HTTPS)
- Kestrel runs in the background on port 8080, not directly exposed externally
# Enable site
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Q3: How to Configure HTTPS (SSL/TLS)?
A: Use Let’s Encrypt free SSL certificates configured on Nginx:
# Install Certbot
sudo apt-get install certbot python3-certbot-nginx
# Auto-configure SSL (Certbot automatically modifies Nginx config)
sudo certbot --nginx -d example.com
# Test auto-renewal
sudo certbot renew --dry-run
Flow after configuration:
Client --HTTPS(443)--> Nginx --HTTP(8080)--> Kestrel
Q4: How to Monitor Application Performance?
A: Several tools available:
- Built-in Tools: Use
dotnet-countersto monitor real-time metrics - APM Tools: Application Insights, New Relic, Datadog
- Open Source: Prometheus + Grafana
# Install dotnet-counters
dotnet tool install --global dotnet-counters
# Find application PID
ps aux | grep dotnet
# Monitor metrics
dotnet-counters monitor -p [PID]
Q5: Application Stops Immediately After Starting?
A: Common causes and solutions:
- ✅ Check Logs:
sudo journalctl -u myproject-api.service -n 50 - ✅ Port Conflict: Ensure port 8080 is not already in use
- ✅ Permission Issues: Verify www-data user has read permissions
- ✅ Configuration Errors: Check appsettings.json format is valid
- ✅ Missing Dependencies: Ensure all required NuGet packages are included
Q6: How to Set Up Multiple Environments (Dev, Staging, Production)?
A: Create separate service files for each environment:
# Production environment
/etc/systemd/system/myproject-api-prod.service
Environment=ASPNETCORE_ENVIRONMENT=Production
# Staging environment
/etc/systemd/system/myproject-api-staging.service
Environment=ASPNETCORE_ENVIRONMENT=Staging
ExecStart=/usr/bin/dotnet /path/staging/MyProject.APIService.dll --urls=http://0.0.0.0:8081
# Development environment
/etc/systemd/system/myproject-api-dev.service
Environment=ASPNETCORE_ENVIRONMENT=Development
ExecStart=/usr/bin/dotnet /path/dev/MyProject.APIService.dll --urls=http://0.0.0.0:8082
Best Practices
- Use Reverse Proxy: Always use Nginx or Apache as frontend for HTTPS, compression, and caching
- Configure Application Logging: Use Serilog or NLog to write logs to files or centralized logging systems
- Monitoring & Alerting: Set up notifications when systemd service fails
- Regular Backups: Automate backup scripts, periodically clean old backups
- Security Hardening:
- Run application as non-root user
- Limit file permissions (644 for files, 755 for directories)
- Regularly update .NET SDK and system packages
- Configure firewall to only open necessary ports
- Performance Optimization:
- Build with Release configuration
- Enable gzip compression (Nginx level)
- Configure Response Caching and Output Caching
- Use connection pooling for database connections
Summary
This article provides a comprehensive guide for deploying .NET applications on Ubuntu 22.04, covering:
- Web Server Architecture: Kestrel (application server) + Nginx (reverse proxy)
- Build & Publish: Using dotnet CLI for restore, build, publish
- Systemd Management: Creating service files for auto-restart and boot startup
- Troubleshooting: Log checking, port checking, permission checking
- Production Configuration: Reverse proxy, HTTPS, monitoring
Key Takeaways:
- .NET applications use built-in Kestrel web server, no IIS required
- Nginx acts as reverse proxy, forwarding requests to Kestrel
- Always use Release configuration when publishing to production
- Manage services with systemd to ensure high availability
- Configure reverse proxy for HTTPS and load balancing
- Establish comprehensive logging and monitoring mechanisms
- Regularly backup and update applications
Properly configuring these steps ensures .NET applications run stably, securely, and efficiently in Linux environments.
Related Articles
- Ubuntu Server Auto-Update Complete Guide: Enterprise Strategy, Risk Control & Failure Recovery
- Quick Guide to Finding External IP Address on Ubuntu Command Line
- Build RHEL / Rocky Linux Training Environment on Mac: Virtualization Options, Installation Process, and Training Focus
- Timezone Configuration in Cloud Services: From VMs to Containers (English)
- Multipass: Complete Guide to Lightweight Virtual Machine Management Tool