The Ultimate LAMP Stack Guide for Debian 13 (Step-by-Step)

Complete LAMP Stack on Debian 13 – DevAnswers.net

We may earn a referral fee at no additional cost to you when you shop through the links on this page.

This comprehensive guide walks you through building a secure, production-ready LAMP stack on Debian 13 (Trixie), the current stable release.

Using native repository packages, you’ll install Apache 2.4, PHP 8.4, MariaDB 11.8, secure the site with free Let’s Encrypt SSL, and optionally add phpMyAdmin or WordPress. All optimized for Debian’s renowned stability and security.

Step 1 – Install Apache 

The following steps cover installing the Apache web server, configuring UFW firewall rules for web traffic, verifying the service, and setting up a virtual host for your domain.

Installing Apache 

Update the local package index to ensure the package manager has the latest information about available software versions, then install the apache2 package:

Terminal
sudo apt update && sudo apt install -y apache2

This installs Apache 2.4 from the Debian 13 (Trixie) repositories. The server starts automatically after installation.

Adjusting the Firewall 

Before testing Apache, configure a firewall to allow external access to web ports while restricting other traffic. This guide uses UFW (Uncomplicated Firewall), which works identically on Ubuntu and Debian.

If you prefer not to use a firewall (e.g., local development or behind a cloud provider’s firewall), you can skip this step entirely.

Allow Apache through the firewall:

Terminal
sudo ufw allow 'WWW Full'

Verify the status:

Terminal
sudo ufw status

You should see Apache listed as well as OpenSSH if you’re using it.

Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
WWW Full                   ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
WWW Full (v6)              ALLOW       Anywhere (v6)

Verifying the Installation 

Apache starts automatically after installation. Confirm the service is active:

Terminal
sudo systemctl status apache2

You should see active (running) in the output.

● apache2.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-02-02 20:00:56 GMT; 1h 13min ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 5912 (apache2)
      Tasks: 55 (limit: 503)
     Memory: 6.6M (peak: 6.8M)
        CPU: 372ms
     CGroup: /system.slice/apache2.service
             ├─5912 /usr/sbin/apache2 -k start
             ├─5915 /usr/sbin/apache2 -k start
             └─5916 /usr/sbin/apache2 -k start

Feb 02 20:00:55 devanswers.net systemd[1]: Starting apache2.service - The Apache HTTP Server...
Feb 02 20:00:56 devanswers.net apachectl[5911]: AH00558: apache2: Could not reliably determine the ser>
Feb 02 20:00:56 devanswers.net systemd[1]: Started apache2.service - The Apache HTTP Server.
On some terminals, you may need to press q to exit this screen.
If you see “Could not reliably determine the server’s fully qualified domain name,” you can safely ignore it. This won’t affect functionality.

Test locally or from a remote machine:

Don’t know your IP? Run hostname -I for your local IP, or curl -4 icanhazip.com for your public IP on a VPS.
Terminal
curl http://localhost
# or from remote:
curl http://your_server_ip

If you see HTML output in the response, the install was successful.

If you have access to a browser, you should see the default Apache placeholder page featuring the Debian logo and the message ‘It works!’.

The default Apache welcome page is located at /var/www/html/index.html. To edit it:

Terminal
sudo nano /var/www/html/index.html

Save and exit (press CTRL + X, press Y, then press ENTER)

Managing the Apache Service 

Essential systemd commands for managing Apache:

ActionCommandNotes
Stopsudo systemctl stop apache2Halts the service completely
Startsudo systemctl start apache2Starts if stopped
Restartsudo systemctl restart apache2Full restart (brief downtime)
Reloadsudo systemctl reload apache2Graceful reload (preferred for config changes)
Enable on bootsudo systemctl enable apache2Auto-start after reboot
Use reload instead of restart in production to avoid downtime when applying configuration changes.

Important File Locations 

Key paths to know for managing Apache on Debian 13 (Trixie):

  • Default Web Root: /var/www/html — Your web content goes here.
  • Main Configuration: /etc/apache2/apache2.conf — Global configuration file.
  • Ports Configuration: /etc/apache2/ports.conf — Defines listening ports (80, 443).
  • Virtual Hosts: /etc/apache2/sites-available/ — Per-site configurations.
  • Access Log: /var/log/apache2/access.log — All requests to your server.
  • Error Log: /var/log/apache2/error.log — Errors and diagnostics.

Setting Up a Virtual Host 

To serve content for your domain instead of the default page, configure a virtual host.

Ensure your domain’s DNS A record points to your server’s public IP (configured at your domain registrar). Propagation can take up to 48 hours. For local testing, edit your /etc/hosts file on your client machine.

Create a Directory for Your Site 

Terminal
sudo mkdir -p /var/www/example.com

Create a Sample Page 

Terminal
echo '<html><head><title>Success</title></head><body><h1>Success! The Virtual Host is working.</h1><p>You can now replace this file with your own content.</p><p>Return to <a href="https://devanswers.net/series/lamp-stack/">LAMP Stack Documentation</a></p></body></html>' | sudo tee /var/www/example.com/index.html

Set Permissions 

Assign ownership to the web server user and configure group permissions:

Terminal
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 775 /var/www/example.com
sudo chmod -R g+s /var/www/example.com

Add your user to the www-data group so you can modify files without sudo:

Terminal
sudo usermod -aG www-data $USER
Log out and back in for the group membership to take effect. Verify with id.

Create the Virtual Host Configuration 

Terminal
sudo nano /etc/apache2/sites-available/example.com.conf

Paste the following configuration (replace example.com with your domain):

example.com.conf
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com

    <Directory /var/www/example.com>
        Options FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

Save and exit (press CTRL + X, press Y, then press ENTER)

Options includes only FollowSymLinks (no Indexes). This deliberately disables directory browsing for security. Enabling Indexes can expose your file structure if an index file is missing.

Enable the Site and Reload Apache 

Enable your virtual host, disable the default site, and enable the rewrite module (required for user-friendly URLs in WordPress and modern frameworks):

Terminal
sudo a2ensite example.com.conf
sudo a2dissite 000-default.conf
sudo a2enmod rewrite
sudo apache2ctl configtest && sudo systemctl reload apache2
The a2enmod output may say “you need to run: systemctl restart apache2”. A reload is sufficient here and avoids a brief service interruption.

Test It 

Visit http://example.com in your browser. As long as your domain’s A record points to your server’s IP, you should see the sample page.

Browser at http://example.com showing the sample page: Success! The Virtual Host is working.

Conclusion 

You now have Apache 2.4 running on Debian 13 (Trixie) with a configured virtual host.

Step 2 – Install MySQL 

The following steps cover installing and securing MariaDB 11.8, creating a dedicated database and application user with scoped privileges, and verifying network security settings.

MariaDB is Debian’s default: Since Debian 9, MariaDB has been the default database server in Debian repositories. It’s a community fork of MySQL, created in 2009, and the two are functionally interchangeable for nearly all use cases. Your PHP applications, WordPress installations, and frameworks won’t know the difference.

Installing MariaDB 

Start by refreshing your package index, then install the MariaDB server:

Terminal
sudo apt update
Terminal
sudo apt install -y mariadb-server

This installs MariaDB 11.8 from the Debian 13 (Trixie) repositories. The server starts automatically after installation.

Verifying the Installation 

Confirm the service is running:

Terminal
sudo systemctl status mariadb

You should see active (running) in the output. If the service is not running, start it manually:

Terminal
sudo systemctl start mariadb

Verify the installed version:

Terminal
mariadb --version

Understanding the Default Security Model 

Modern MariaDB installations use unix socket authentication for the root database user. The database root account can only be accessed by the Linux root user (or via sudo), so no password is required for local administrative access.

This approach is more secure than traditional password authentication: there’s no password to guess, leak, or brute-force, and remote root access is impossible by design.

To access the database shell as administrator:

Terminal
sudo mariadb

You’ll enter the MariaDB prompt immediately without a password prompt. This is expected behavior.

Running the Security Script 

MariaDB includes a hardening script that removes insecure defaults. While recent versions ship with secure settings already applied, running this script confirms your configuration:

Terminal
sudo mariadb-secure-installation

The script asks several questions. Here’s how to answer each:

Switch to unix_socket authentication? Answer Yes. This ensures the most secure local access method: only the Linux root user (via sudo) can log into the MariaDB root account, with no password needed or accepted. On some cloud provider images, unix socket may not be active yet, so answering Yes here guarantees it is enabled.

Change the root password? Answer No for most setups. With unix socket authentication, the root password is irrelevant for local access. You can set one as an emergency backup, but it’s not required.

Remove anonymous users? Answer Yes. Anonymous users could allow unintended access.

Disallow root login remotely? Answer Yes. The root database user should only connect locally. For remote access, create a dedicated user with limited privileges.

Remove test database? Answer Yes. The test database is accessible to anyone and serves no production purpose.

Reload privilege tables? Answer Yes. This applies your changes immediately.

MariaDB 11.8 on Debian 13 ships with secure defaults. Some cloud provider images may use password authentication for root initially; the security script’s unix_socket prompt corrects this. If the script skips certain prompts, it means those settings are already secure.

Creating a Database and Application User 

Never use the root database account for application connections. Instead, create a dedicated database and user with only the privileges that application needs.

Access the database shell:

Terminal
sudo mariadb

Create a new database (replace example_db with your application’s name):

MySQL
CREATE DATABASE example_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

The utf8mb4 character set supports the full Unicode range, including emoji, which is essential for modern web applications.

Create a user and grant privileges on that database only (replace the placeholders with your values):

MySQL
CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'your_secure_password_here';
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
FLUSH PRIVILEGES;

The 'localhost' host restriction means this user can only connect from the local server, not remotely. For applications running on the same machine, this is exactly what you want.

Need remote database access? Replace 'localhost' with the specific IP address that needs to connect (e.g., '192.168.1.50'), or use '%' for any host (less secure). You’ll also need to modify the bind-address setting in the MariaDB configuration.

Exit the database shell:

MySQL
EXIT;

Testing the Application User 

Verify your new user can connect and access only the intended database:

Terminal
mariadb -u example_user -p

Enter the password you set. Once connected, verify access:

MySQL
SHOW DATABASES;

You should see only example_db and information_schema (a read-only system database). The user cannot see or access other databases.

Verifying Network Security 

By default, MariaDB listens only on localhost (127.0.0.1), meaning it accepts connections only from the local machine. Verify this:

Terminal
sudo ss -tlnp | grep 3306

You should see output showing 127.0.0.1:3306 or [::1]:3306 (IPv6 localhost). If you see 0.0.0.0:3306 or *:3306, the database is exposed to all network interfaces, which is a security risk unless you specifically need remote connections.

The bind address is configured in /etc/mysql/mariadb.conf.d/50-server.cnf. Look for the bind-address directive. Leave it as 127.0.0.1 unless you specifically need remote connections.

Connecting Applications 

Your applications can connect to MariaDB using either a TCP connection or a Unix socket. For applications on the same server, the socket connection is slightly faster.

Socket connection (recommended for local applications):

PHP
$pdo = new PDO(
 'mysql:unix_socket=/run/mysqld/mysqld.sock;dbname=example_db',
 'example_user',
 'your_secure_password_here'
);

TCP connection (works for local or remote):

PHP
$pdo = new PDO(
 'mysql:host=127.0.0.1;port=3306;dbname=example_db',
 'example_user',
 'your_secure_password_here'
);

Both examples use mysql: as the DSN prefix. MariaDB uses the MySQL protocol, so PHP’s PDO MySQL driver works without modification.

Common connection issues:

  • Access denied: Verify username, password, and host. Remember that 'user'@'localhost' and 'user'@'127.0.0.1' are treated as different accounts.
  • Can’t connect to socket: Ensure MariaDB is running (sudo systemctl status mariadb) and the socket path is correct.
  • Connection refused: Check if the server is listening on the expected interface with ss -tlnp | grep 3306.

Conclusion 

You have successfully installed and secured MariaDB on Debian 13 (Trixie) and created a dedicated database with a least-privilege application user.

Step 3 – Install PHP 

The following steps cover installing PHP, configuring essential extensions for web applications, optimising PHP settings, and verifying the installation with Apache.

Installing PHP 

Install PHP and the Apache module that allows Apache to process PHP files:

Terminal
sudo apt update && sudo apt install -y php libapache2-mod-php

This installs PHP 8.4 from the Debian 13 (Trixie) repositories along with the Apache PHP module (libapache2-mod-php), which enables Apache to interpret PHP code. The module is automatically enabled and Apache is restarted during installation.

Verifying the Installation 

Check the installed PHP version:

Terminal
php -v

You should see output similar to:

Output
PHP 8.4.x (cli) (built: ...)
Copyright (c) The PHP Group
Zend Engine v4.x.x, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.x, Copyright (c), by Zend Technologies

Testing PHP with Apache 

Create a PHP test file to verify Apache processes PHP correctly:

Terminal
echo '<?php phpinfo(); ?>' | sudo tee /var/www/html/info.php

Open your browser and navigate to:

http://your_server_ip/info.php

You should see the PHP information page displaying your PHP version, loaded modules, and configuration details.

Security warning: The phpinfo() page exposes sensitive server configuration details. Remove it after testing:

sudo rm /var/www/html/info.php

Installing Common PHP Extensions 

Most web applications require additional PHP extensions. The following command installs a robust set of extensions needed by WordPress, Laravel, Drupal, and other popular frameworks:

Terminal
sudo apt install -y php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl php-bcmath

Here’s what each extension provides:

ExtensionPurposeRequired By
php-mysqlMySQL/MariaDB database connectivityWordPress, Laravel, Drupal, most CMS
php-curlHTTP requests, API integrationsWordPress plugins, payment gateways
php-gdImage processing and manipulationWordPress media, image galleries
php-mbstringMultibyte string handling (UTF-8)Any multilingual application
php-xmlXML parsing (RSS, sitemaps, SOAP)WordPress, Drupal, API integrations
php-zipArchive compression/extractionPlugin/theme installers
php-intlInternationalization (i18n)Symfony, Laravel, Magento
php-bcmathArbitrary precision mathematicsE-commerce, financial applications

Restart Apache to load the new extensions:

Terminal
sudo systemctl restart apache2

Verify the extensions are loaded:

Terminal
php -m | grep -iE 'mysql|curl|gd|mbstring|xml|zip|intl|bcmath'

You should see each extension listed in the output. If any are missing, re-run the install command and restart Apache.

Need to find an extension? Search available packages with:

apt search php- | grep ^php

Setting PHP as Apache’s Default 

By default, Apache looks for index.html before index.php when serving a directory. For PHP applications, you typically want index.php to take priority.

Edit the Apache directory index configuration:

Terminal
sudo nano /etc/apache2/mods-enabled/dir.conf

You’ll see a line similar to:

Apache
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm

Move index.php to the front:

dir.conf
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm

Save and exit (press CTRL + X, press Y, then press ENTER)

Test the configuration and reload Apache:

Terminal
sudo apache2ctl configtest && sudo systemctl reload apache2

Configuring PHP 

PHP’s configuration file controls memory limits, upload sizes, error reporting, and other runtime settings. The primary configuration file for Apache is:

/etc/php/8.4/apache2/php.ini

Common Configuration Changes 

Open the PHP configuration file:

Terminal
sudo nano /etc/php/8.4/apache2/php.ini

Here are recommended settings for most web applications. Find each directive (use Ctrl + W to search) and modify as needed:

php.ini
; Maximum memory a script can consume (increase for large applications)
memory_limit = 256M

; Maximum file upload size (adjust for media-heavy sites)
upload_max_filesize = 64M

; Maximum size of POST data (should be >= upload_max_filesize)
post_max_size = 64M

; Maximum script execution time in seconds
max_execution_time = 300

; Maximum time for parsing input data
max_input_time = 300

; Server timezone (important for date/time functions)
date.timezone = UTC

Save and exit (press CTRL + X, press Y, then press ENTER)

These values are suggestions. Adjust based on your application’s requirements and server resources. WordPress typically needs at least 128M memory; WooCommerce and page builders often require 256M or more.

Restart Apache to apply configuration changes:

Terminal
sudo systemctl restart apache2

Production Error Handling 

In production, disable error display to prevent exposing sensitive information while still logging errors for debugging:

php.ini
; Disable error display in browser (security)
display_errors = Off

; Log errors to file instead
log_errors = On

; Error log location (check Apache's error log or set custom path)
; error_log = /var/log/php_errors.log

Save and exit (press CTRL + X, press Y, then press ENTER)

During development, set display_errors = On temporarily to see errors in your browser. Always switch back to Off before going live.

Important File Locations 

Key paths for managing PHP on Debian 13 (Trixie):

  • PHP Configuration (Apache): /etc/php/8.4/apache2/php.ini — Primary settings file.
  • PHP Configuration (CLI): /etc/php/8.4/cli/php.ini — Settings for command-line scripts.
  • Apache PHP Module Config: /etc/apache2/mods-available/php8.4.conf — Module-specific settings.
  • Additional Configuration: /etc/php/8.4/apache2/conf.d/ — Drop-in config files for extensions.
  • PHP Error Log: Typically /var/log/apache2/error.log unless configured otherwise.

Creating a Complete Test 

Create a simple PHP script to verify your setup works end-to-end. If you set up a virtual host in the Apache guide, place this in your site directory (this replaces the Apache test page you created earlier):

Terminal
sudo nano /var/www/example.com/index.php

Add the following content:

index.php
<?php
$hostname = gethostname();
$php_version = phpversion();
$server_software = $_SERVER['SERVER_SOFTWARE'];
$document_root = $_SERVER['DOCUMENT_ROOT'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PHP Test - <?php echo $hostname; ?></title>
    <style>
        body { font-family: system-ui, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
        h1 { color: #4F5D95; }
        .info { background: #f4f4f4; padding: 15px; border-radius: 5px; margin: 10px 0; }
        .success { color: #28a745; }
        code { background: #e9ecef; padding: 2px 6px; border-radius: 3px; }
    </style>
</head>
<body>
    <h1>✓ PHP is Working!</h1>
    <div class="info">
        <p><strong>PHP Version:</strong> <code><?php echo $php_version; ?></code></p>
        <p><strong>Server:</strong> <code><?php echo $server_software; ?></code></p>
        <p><strong>Hostname:</strong> <code><?php echo $hostname; ?></code></p>
        <p><strong>Document Root:</strong> <code><?php echo $document_root; ?></code></p>
    </div>
    <p class="success">Your LAMP stack's PHP component is configured correctly.</p>
    <p><small>Return to <a href="https://devanswers.net/series/lamp-stack/">LAMP Stack Documentation</a></small></p>
</body>
</html>

Save and exit (press CTRL + X, press Y, then press ENTER)

Visit http://example.com in your browser to see the test page. If you did not set up a virtual host, use http://your_server_ip instead (and place the file in /var/www/html/index.php).

Conclusion 

You now have PHP 8.4 running with Apache on Debian 13, complete with essential extensions for modern web applications.

Step 4 – Secure with SSL 

The following steps cover installing Certbot, obtaining a free Let’s Encrypt SSL certificate for your domain, configuring Apache for HTTPS with automatic HTTP redirection, enabling HSTS security headers, and setting up automatic certificate renewal.

Installing Certbot 

Certbot is the official Let’s Encrypt client that automates certificate issuance and Apache configuration. Install it along with the Apache plugin:

Terminal
sudo apt update && sudo apt install -y certbot python3-certbot-apache

Verify the installation:

Terminal
certbot --version

You should see the Certbot version number in the output, confirming a successful installation.

Obtaining an SSL Certificate 

Before requesting a certificate, verify that your domain resolves to your server’s IP address:

Terminal
host example.com

The output should show your server’s public IP. If it doesn’t, update your DNS A record at your domain registrar and wait for propagation before continuing.

DNS must be pointed correctly. Let’s Encrypt verifies domain ownership by making an HTTP request to your server on port 80. If your domain doesn’t resolve to this server, the certificate request will fail. DNS propagation can take up to 48 hours after changes.

Run Certbot with the Apache plugin to obtain and install your certificate. Replace example.com with your actual domain:

Terminal
sudo certbot --apache -d example.com -d www.example.com
If you only use one domain (no www subdomain), omit the second -d flag. You can also secure multiple subdomains by adding additional -d flags (e.g., -d blog.example.com).

Certbot will prompt you for the following:

  1. Email address — for certificate expiry notifications and account recovery. Enter a valid email.
  2. Terms of Service — you must agree (Y) to proceed.
  3. EFF mailing list — optional. Enter Y or N based on your preference.

If successful, you will see output confirming the certificate was issued and deployed:

Output
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2026-05-20.

Deploying certificate
Successfully deployed certificate for example.com
Successfully deployed certificate for www.example.com

Congratulations! You have successfully enabled HTTPS on
https://example.com and https://www.example.com
What Certbot did behind the scenes:
  • Obtained a trusted certificate from Let’s Encrypt via an HTTP-01 challenge
  • Created an SSL virtual host at /etc/apache2/sites-available/example.com-le-ssl.conf
  • Enabled Apache’s mod_ssl and mod_rewrite modules
  • Configured HTTP-to-HTTPS redirection in your original virtual host
  • Applied recommended TLS settings from /etc/letsencrypt/options-ssl-apache.conf
  • Reloaded Apache to apply all changes
Certbot failed? Common causes:
  • Domain not resolving: Verify your DNS A record points to this server’s IP
  • Port 80 blocked: Ensure sudo ufw status shows WWW Full allowed
  • No matching virtual host: Certbot needs an Apache virtual host with a ServerName matching your domain
  • Rate limit exceeded: Let’s Encrypt allows 5 duplicate certificates per domain per week. Wait and retry later.

Verifying HTTPS 

Open your browser and visit https://example.com. You should see your site served over HTTPS with a padlock icon in the address bar.

From the command line, verify the HTTPS response headers:

Terminal
curl -sI https://example.com

You should see HTTP/2 200 (or HTTP/1.1 200 OK) at the top of the response.

Next, verify that HTTP requests are automatically redirected to HTTPS:

Terminal
curl -sI http://example.com

You should see a 301 Moved Permanently response with a Location header pointing to https://example.com/. This confirms that all HTTP traffic is being redirected to the secure HTTPS version.

For a comprehensive security assessment, test your domain at SSL Labs. With Certbot’s default TLS configuration, you should receive an A or A+ rating.

Strengthening SSL Security 

Certbot’s default configuration provides solid SSL security, including modern TLS protocols and cipher suites managed via /etc/letsencrypt/options-ssl-apache.conf. To further protect your site, enable HTTP Strict Transport Security (HSTS), which instructs browsers to always use HTTPS even if a user types http:// in the address bar.

First, enable the Apache headers module:

Terminal
sudo a2enmod headers

Create a configuration file for SSL security headers:

Terminal
sudo nano /etc/apache2/conf-available/ssl-headers.conf

Add the following content:

ssl-headers.conf
<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000"
</IfModule>

Save and exit (press CTRL + X, press Y, then press ENTER)

Using a separate configuration file (rather than editing Certbot’s virtual host) keeps your customizations cleanly separated from Certbot-managed files and applies the header to all SSL virtual hosts on this server.

Enable the configuration, test, and reload Apache:

Terminal
sudo a2enconf ssl-headers
sudo apache2ctl configtest && sudo systemctl reload apache2

Verify the HSTS header is present:

Terminal
curl -sI https://example.com | grep -i strict

You should see:

Output
strict-transport-security: max-age=31536000
The max-age=31536000 value tells browsers to remember the HTTPS-only policy for one year (31,536,000 seconds). Once your site is stable on HTTPS, you can optionally add includeSubDomains and submit your domain to the HSTS preload list for browser-level enforcement. Only do this when you are certain all subdomains support HTTPS.

Automatic Certificate Renewal 

Let’s Encrypt certificates are valid for 90 days. Certbot installs a systemd timer that automatically checks for renewal twice daily and renews any certificate within 30 days of expiry.

Verify the renewal timer is active:

Terminal
sudo systemctl status certbot.timer

You should see active (waiting) in the output, along with the next scheduled run time.

Test the renewal process with a dry run (simulates renewal without making any changes):

Terminal
sudo certbot renew --dry-run

If the dry run completes without errors, automatic renewal is working correctly. You should see Congratulations, all simulated renewals succeeded in the output.

Certbot handles everything automatically. You do not need to set up cron jobs or manually renew certificates. If renewal fails, Certbot sends a notification to the email address you provided during setup.

Important File Locations 

Key paths for managing SSL certificates on Debian 13 (Trixie):

  • Certificate (full chain): /etc/letsencrypt/live/example.com/fullchain.pem — The complete certificate chain sent to browsers.
  • Private Key: /etc/letsencrypt/live/example.com/privkey.pem — Your certificate’s private key. Keep this secure.
  • Apache SSL Virtual Host: /etc/apache2/sites-available/example.com-le-ssl.conf — Certbot-generated HTTPS configuration.
  • Renewal Configuration: /etc/letsencrypt/renewal/example.com.conf — Settings used for automatic renewal.
  • SSL Options: /etc/letsencrypt/options-ssl-apache.conf — Certbot-maintained TLS protocol and cipher settings.
  • HSTS Headers: /etc/apache2/conf-available/ssl-headers.conf — Custom security headers (created above).
  • Certbot Log: /var/log/letsencrypt/letsencrypt.log — Detailed log for troubleshooting certificate operations.

Conclusion 

Your Debian 13 (Trixie) server is now secured with a free Let’s Encrypt SSL certificate, automatic HTTP-to-HTTPS redirection, HSTS security headers, and automated 90-day certificate renewal.

Summary – What You Now Have 

Congratulations! You now have a complete LAMP stack running on Debian 13 with Apache 2.4, PHP 8.4, MariaDB 11.8, and a free Let’s Encrypt SSL certificate securing all traffic over HTTPS.

As Debian’s current stable release, this provides cutting-edge software with the long-term stability and security support Debian is renowned for.

Support DevAnswers
Written, tested and maintained by one developer. Your support keeps these guides accurate and ad-light.
Buy me a coffee

Comments

Leave a reply