⚡ LAMP Stack: C# (.NET) Installation & Configuration

About this Guide: This document covers the complete installation and configuration of C# and .NET as an alternative "P" in your LAMP stack (Linux, Apache, MySQL, C#). We'll cover installing .NET SDK/Runtime, configuring Apache as a reverse proxy to Kestrel, setting up ASP.NET Core applications, and database connectivity.

Table of Contents

Why C# on Linux?

With .NET Core (now just ".NET" as of version 5+), C# has become a first-class citizen on Linux:

Architecture Note: Unlike PHP/Perl/Python which can run directly in Apache, ASP.NET Core apps run in their own Kestrel web server. Apache acts as a reverse proxy, forwarding requests to Kestrel. This is the recommended production setup.

Prerequisites Check

# 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)

Installing .NET

Red Hat/CentOS/Rocky/Alma Linux 9

# 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

Ubuntu 22.04/24.04

# 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

Verify Installation

# 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
Version Notes:

ASP.NET Core Basics

Create Your First ASP.NET Core App

# 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

Project Structure

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/

Understanding Program.cs (Minimal Hosting Model)

// 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();

Apache Reverse Proxy Setup

Enable Required Apache Modules

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

Configure Apache Virtual Host

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 Apache Configuration

# Test configuration
sudo httpd -t
# or
sudo apache2ctl configtest

# Restart Apache
sudo systemctl restart httpd
# or
sudo systemctl restart apache2

Systemd Service Configuration

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

Publish and Deploy Application

# 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

Configure Kestrel Settings

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;"
  }
}

Database Connectivity

Install MySQL/MariaDB Packages

# 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

Using Entity Framework Core

# 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 Database Context

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; }
    }
}

Configure Database in Program.cs

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 and Run Migrations

# Create initial migration
dotnet ef migrations add InitialCreate

# Update database
dotnet ef database update

# List migrations
dotnet ef migrations list

Complete Sample Application

Create API Controller

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();
        }
    }
}

Update Program.cs for API

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();

Security & Production Setup

SELinux Configuration (Red Hat/CentOS)

# 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

Firewall Configuration

# 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

Application Security Best Practices

Production Security Checklist:

Configure User Secrets (Development)

# 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

Environment-Specific Configuration

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"
      }
    }
  }
}

Troubleshooting

Common Issues

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

Debugging Commands

# 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

Enable Detailed Logging

Edit appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft.AspNetCore": "Debug",
      "Microsoft.EntityFrameworkCore": "Debug"
    }
  }
}

Performance Monitoring

# 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)

Quick Reference

Essential .NET Commands

# 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

Service Management

# 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

✓ C# LAMP Stack Complete!

Your Linux + Apache + MySQL + C# stack is now configured. You have:

Next Steps:

  1. Test your application at http://yourdomain.com
  2. Set up SSL with Let's Encrypt (certbot)
  3. Configure authentication (ASP.NET Core Identity)
  4. Set up monitoring and logging
  5. Consider Docker containerization
Resources:

LAMP Stack - C# (.NET) Configuration Guide
For object-oriented development on Linux with Apache and MySQL

← Back to LAMPS Index ↑ Back to EXPANDED