With .NET Core (now just ".NET" as of version 5+), C# has become a first-class citizen on Linux:
# Check if Apache/HTTPD is installed httpd -v # or apache2 -v # Check Apache status systemctl status httpd # or systemctl status apache2 # Verify MySQL/MariaDB is installed mysql --version # Check system architecture (important for .NET) uname -m # Should show: x86_64 or aarch64 (ARM64)
# Add Microsoft package repository
sudo dnf install -y dotnet-sdk-8.0
# Or manually add the repo
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo wget -O /etc/yum.repos.d/microsoft-prod.repo \
https://packages.microsoft.com/config/rhel/9/prod.repo
# Install .NET SDK (includes runtime)
sudo dnf install -y dotnet-sdk-8.0
# Or install just the runtime (for hosting only)
sudo dnf install -y aspnetcore-runtime-8.0
# Install via apt (Ubuntu 22.04+) sudo apt update sudo apt install -y dotnet-sdk-8.0 # Or manually add Microsoft repository wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb # Update package list sudo apt update # Install .NET SDK sudo apt install -y dotnet-sdk-8.0 # Or just ASP.NET Core runtime sudo apt install -y aspnetcore-runtime-8.0
# Check .NET version dotnet --version # Should show: 8.0.x # List installed SDKs dotnet --list-sdks # List installed runtimes dotnet --list-runtimes # Check info dotnet --info
# Create a directory for your app mkdir -p /var/www/myapp cd /var/www/myapp # Create new web app dotnet new webapp -n MyFirstApp # Or create Web API # dotnet new webapi -n MyFirstApp # Or create MVC app # dotnet new mvc -n MyFirstApp cd MyFirstApp # Build the application dotnet build # Run the application (development mode) dotnet run # The app will start on http://localhost:5000 and https://localhost:5001
MyFirstApp/
├── Program.cs # Application entry point
├── appsettings.json # Configuration
├── appsettings.Development.json
├── MyFirstApp.csproj # Project file
├── Properties/
│ └── launchSettings.json # Development settings
├── wwwroot/ # Static files (CSS, JS, images)
│ ├── css/
│ ├── js/
│ └── lib/
└── Pages/ # Razor Pages (for webapp template)
├── Index.cshtml
├── Index.cshtml.cs
└── Shared/
// Modern .NET 6+ uses top-level statements
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Red Hat/CentOS/Rocky/Alma:
# Modules should already be available # Verify they're enabled httpd -M | grep proxy # Should see proxy_module and proxy_http_module
Ubuntu/Debian:
# Enable proxy modules sudo a2enmod proxy sudo a2enmod proxy_http sudo a2enmod proxy_wstunnel # For WebSocket support sudo a2enmod headers sudo a2enmod rewrite # Restart Apache sudo systemctl restart apache2
For Red Hat/CentOS/Rocky/Alma:
sudo vi /etc/httpd/conf.d/dotnet-app.conf
For Ubuntu/Debian:
sudo vi /etc/apache2/sites-available/dotnet-app.conf
Add the following configuration:
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
# Proxy settings
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
# WebSocket support (if needed)
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://127.0.0.1:5000%{REQUEST_URI} [P]
# Headers
RequestHeader set X-Forwarded-Proto "http"
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
# Logging
ErrorLog /var/log/httpd/dotnet-app-error.log
CustomLog /var/log/httpd/dotnet-app-access.log combined
</VirtualHost>
# SSL/HTTPS Configuration (recommended for production)
<VirtualHost *:443>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/yourdomain.crt
SSLCertificateKeyFile /etc/pki/tls/private/yourdomain.key
# SSLCertificateChainFile /etc/pki/tls/certs/chain.crt
# Proxy settings
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
# Headers
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
# WebSocket support
RewriteEngine on
RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC]
RewriteRule .* ws://127.0.0.1:5000%{REQUEST_URI} [P]
# Logging
ErrorLog /var/log/httpd/dotnet-app-ssl-error.log
CustomLog /var/log/httpd/dotnet-app-ssl-access.log combined
</VirtualHost>
For Ubuntu/Debian, enable the site:
sudo a2ensite dotnet-app sudo systemctl reload apache2
# Test configuration sudo httpd -t # or sudo apache2ctl configtest # Restart Apache sudo systemctl restart httpd # or sudo systemctl restart apache2
Create a systemd service to run your .NET app automatically:
sudo vi /etc/systemd/system/myapp.service
Add the following:
[Unit] Description=My ASP.NET Core Application After=network.target [Service] Type=notify # User to run the service as User=www-data # Or on Red Hat/CentOS # User=apache WorkingDirectory=/var/www/myapp/MyFirstApp ExecStart=/usr/bin/dotnet /var/www/myapp/MyFirstApp/bin/Release/net8.0/MyFirstApp.dll # Restart service after 10 seconds if it crashes Restart=always RestartSec=10 # Security settings # NoNewPrivileges=true # PrivateTmp=true # Environment variables Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false # Logging SyslogIdentifier=myapp StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
# Build for production (in your app directory) cd /var/www/myapp/MyFirstApp dotnet publish -c Release -o bin/Release/net8.0/publish # Set proper ownership sudo chown -R apache:apache /var/www/myapp # or on Ubuntu/Debian sudo chown -R www-data:www-data /var/www/myapp # Set permissions sudo chmod -R 755 /var/www/myapp # Reload systemd sudo systemctl daemon-reload # Enable service to start on boot sudo systemctl enable myapp # Start the service sudo systemctl start myapp # Check status sudo systemctl status myapp # View logs sudo journalctl -u myapp -f
Edit appsettings.json in your application:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://127.0.0.1:5000"
}
},
"Limits": {
"MaxConcurrentConnections": 100,
"MaxRequestBodySize": 10485760
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=myapp;User=myuser;Password=mypassword;"
}
}
# Add MySQL connector package cd /var/www/myapp/MyFirstApp dotnet add package MySql.Data dotnet add package MySqlConnector # Or use Entity Framework Core with MySQL dotnet add package Pomelo.EntityFrameworkCore.MySql # or dotnet add package MySql.EntityFrameworkCore
# Install EF Core tools globally dotnet tool install --global dotnet-ef # Add EF Core packages to your project dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Pomelo.EntityFrameworkCore.MySql
Create Data/ApplicationDbContext.cs:
using Microsoft.EntityFrameworkCore;
namespace MyFirstApp.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using MyFirstApp.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddRazorPages();
// Add database context
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(connectionString,
ServerVersion.AutoDetect(connectionString)));
var app = builder.Build();
// Configure middleware
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
# Create initial migration dotnet ef migrations add InitialCreate # Update database dotnet ef database update # List migrations dotnet ef migrations list
Create Controllers/UsersController.cs:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MyFirstApp.Data;
namespace MyFirstApp.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly ApplicationDbContext _context;
public UsersController(ApplicationDbContext context)
{
_context = context;
}
// GET: api/users
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
// GET: api/users/5
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null)
{
return NotFound();
}
return user;
}
// POST: api/users
[HttpPost]
public async Task<ActionResult<User>> CreateUser(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
// PUT: api/users/5
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(int id, User user)
{
if (id != user.Id)
{
return BadRequest();
}
_context.Entry(user).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
// DELETE: api/users/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null)
{
return NotFound();
}
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return NoContent();
}
}
}
using Microsoft.EntityFrameworkCore;
using MyFirstApp.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Database
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
# Allow Apache to make network connections sudo setsebool -P httpd_can_network_connect 1 # Set proper SELinux contexts sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/myapp(/.*)?" sudo restorecon -Rv /var/www/myapp/ # If you get permission errors, check audit log sudo ausearch -m avc -ts recent | grep httpd
# Open HTTP and HTTPS ports sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload # Or specific ports sudo firewall-cmd --permanent --add-port=80/tcp sudo firewall-cmd --permanent --add-port=443/tcp sudo firewall-cmd --reload
ASPNETCORE_ENVIRONMENT=Production# Initialize user secrets cd /var/www/myapp/MyFirstApp dotnet user-secrets init # Add a secret dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=mydb;User=user;Password=secret;" # List secrets dotnet user-secrets list
Create appsettings.Production.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "yourdomain.com,www.yourdomain.com",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://127.0.0.1:5000"
}
}
}
}
| Problem | Possible Cause | Solution |
|---|---|---|
| 502 Bad Gateway | Kestrel not running | Check systemd service status |
| Connection refused | Wrong port or firewall | Verify port 5000 and firewall rules |
| Permission denied | SELinux or file permissions | Check SELinux contexts and ownership |
| Database connection failed | Wrong connection string | Verify credentials and server |
| DLL not found | Publish not run | Run dotnet publish |
# Check systemd service status sudo systemctl status myapp # View application logs sudo journalctl -u myapp -f # View Apache error logs sudo tail -f /var/log/httpd/error_log # or sudo tail -f /var/log/apache2/error.log # Check if Kestrel is listening sudo netstat -tlnp | grep :5000 # or sudo ss -tlnp | grep :5000 # Test locally curl http://localhost:5000 # Check .NET diagnostics dotnet --info # Verify published files ls -la /var/www/myapp/MyFirstApp/bin/Release/net8.0/publish/ # Check SELinux denials sudo ausearch -m avc -ts recent # Test database connection mysql -h localhost -u myuser -p myapp
Edit appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Debug",
"Microsoft.EntityFrameworkCore": "Debug"
}
}
}
# Monitor application performance dotnet-counters monitor --process-id $(pgrep -f MyFirstApp) # Or install and use dotnet-trace dotnet tool install --global dotnet-trace dotnet-trace collect --process-id $(pgrep -f MyFirstApp)
# Create new project dotnet new webapp -n MyApp # Build project dotnet build # Run project (development) dotnet run # Publish for production dotnet publish -c Release # Add NuGet package dotnet add package PackageName # List packages dotnet list package # Entity Framework migrations dotnet ef migrations add MigrationName dotnet ef database update # Check .NET version dotnet --version # Clean build artifacts dotnet clean
# Start service sudo systemctl start myapp # Stop service sudo systemctl stop myapp # Restart service sudo systemctl restart myapp # Enable on boot sudo systemctl enable myapp # View status sudo systemctl status myapp # View logs sudo journalctl -u myapp -f # Reload after config change sudo systemctl daemon-reload
Your Linux + Apache + MySQL + C# stack is now configured. You have:
Next Steps:
LAMP Stack - C# (.NET) Configuration Guide
For object-oriented development on Linux with Apache and MySQL