Skip to main content

Overview

By default, ipMoodle runs on HTTP (port 80). For production deployments, you should enable HTTPS to secure communications between users and your Moodle server. This guide covers two approaches:
  • Let’s Encrypt - Free, automated SSL certificates (recommended for public domains)
  • Custom Certificates - Self-signed or purchased certificates for internal/private deployments

Prerequisites

  • A registered domain name pointing to your server
  • Ports 80 and 443 open in your firewall
  • Access to modify docker-compose.yml and nginx configuration
Before enabling SSL, ensure your SITE_URL environment variable uses https:// instead of http://

Option 1: Let’s Encrypt with Certbot

Let’s Encrypt provides free SSL certificates with automatic renewal.

Initial Setup

1

Create certificates directory

mkdir -p ./nginx/ssl
mkdir -p ./certbot/conf
mkdir -p ./certbot/www
2

Update docker-compose.yml

Add the certbot service and update the web service volumes (source/docker-compose.yml:50-63):
services:
  # ... existing services ...
  
  web:
    image: nginx:alpine
    container_name: moodle_web
    restart: always
    ports:
      - "80:80"
      - "443:443"  # Add HTTPS port
    volumes:
      - ./html:/var/www/html:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/conf:/etc/letsencrypt:ro  # Add certificate volume
      - ./certbot/www:/var/www/certbot:ro   # Add challenge volume
    depends_on:
      - app
    networks:
      - moodle-net
  
  certbot:
    image: certbot/certbot:latest
    container_name: moodle_certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
3

Update nginx configuration

Create nginx/default.conf with HTTP to HTTPS redirect and SSL configuration:
# HTTP server - redirect to HTTPS
server {
    listen 80;
    server_name your-domain.com;
    
    # Let's Encrypt challenge location
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    
    # Redirect all other traffic to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS server
server {
    listen 443 ssl http2;
    server_name your-domain.com;
    root /var/www/html/public;
    index index.php;
    
    # SSL certificates
    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    
    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;
    
    # SSL session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Optimización de buffers y tiempos
    client_max_body_size 512M;
    fastcgi_read_timeout 300;
    
    # Slash Arguments (VITAL para Moodle)
    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }
    
    # Procesamiento PHP pasando al contenedor "app"
    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param HTTPS on;  # Tell PHP we're using HTTPS
    }
    
    # Seguridad: Bloquear acceso a archivos sensibles
    location ~ (/vendor/|/node_modules/|composer\.json|/readme|/README|/LICENSE|/\.git) {
        deny all;
        return 404;
    }
}
4

Obtain initial certificate

Replace your-domain.com and [email protected] with your actual values:
docker-compose up -d web

docker run --rm \
  -v ./certbot/conf:/etc/letsencrypt \
  -v ./certbot/www:/var/www/certbot \
  certbot/certbot certonly \
  --webroot \
  --webroot-path=/var/www/certbot \
  --email [email protected] \
  --agree-tos \
  --no-eff-email \
  -d your-domain.com \
  -d www.your-domain.com
5

Start all services

docker-compose down
docker-compose up -d

Automatic Certificate Renewal

The certbot container automatically renews certificates every 12 hours. To manually renew:
docker-compose exec certbot certbot renew
Let’s Encrypt certificates are valid for 90 days. The certbot container will automatically renew them when they have 30 days or less remaining.

Option 2: Custom SSL Certificates

For internal deployments or when using purchased certificates:
1

Prepare certificate files

Place your certificate files in ./nginx/ssl/:
  • certificate.crt - Your SSL certificate
  • private.key - Your private key
  • ca_bundle.crt - Certificate authority bundle (optional)
2

Update docker-compose.yml

Add the SSL directory to web service volumes:
web:
  image: nginx:alpine
  container_name: moodle_web
  restart: always
  ports:
    - "80:80"
    - "443:443"
  volumes:
    - ./html:/var/www/html:ro
    - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    - ./nginx/ssl:/etc/nginx/ssl:ro  # Add SSL directory
  depends_on:
    - app
  networks:
    - moodle-net
3

Configure nginx for custom certificates

Update nginx/default.conf:
server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;
    root /var/www/html/public;
    index index.php;
    
    # Custom SSL certificates
    ssl_certificate /etc/nginx/ssl/certificate.crt;
    ssl_certificate_key /etc/nginx/ssl/private.key;
    
    # Include CA bundle if available
    # ssl_trusted_certificate /etc/nginx/ssl/ca_bundle.crt;
    
    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;
    
    # ... rest of the configuration from the current default.conf ...
}
4

Restart services

docker-compose restart web

Generating Self-Signed Certificates

For testing or internal use:
mkdir -p ./nginx/ssl

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout ./nginx/ssl/private.key \
  -out ./nginx/ssl/certificate.crt \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=your-domain.com"
Self-signed certificates will show browser warnings. Only use them for development or internal deployments.

Enhanced SSL Security Configuration

For stronger security, add these directives to your nginx HTTPS server block:
# HSTS (HTTP Strict Transport Security)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

Update Moodle Configuration

After enabling SSL, update your Moodle configuration:
1

Update SITE_URL environment variable

Edit your .env file:
SITE_URL=https://your-domain.com
2

Update config.php

Edit ./html/config.php:
$CFG->wwwroot = 'https://your-domain.com';
$CFG->sslproxy = false; // Set to true if behind SSL-terminating proxy
3

Restart application

docker-compose restart app

Testing SSL Configuration

Verify your SSL setup:
# Test certificate validity
openssl s_client -connect your-domain.com:443 -servername your-domain.com

# Check certificate expiration
echo | openssl s_client -connect your-domain.com:443 2>/dev/null | openssl x509 -noout -dates

# Test SSL rating (external tool)
curl -s https://www.ssllabs.com/ssltest/analyze.html?d=your-domain.com
docker-compose exec web nginx -t
You should see:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Troubleshooting

Certificate errors

Ensure certificate paths in nginx configuration match the mounted volume paths:
ls -la ./nginx/ssl/
ls -la ./certbot/conf/live/your-domain.com/
Let’s Encrypt has rate limits (50 certificates per domain per week). Use staging environment for testing:
--staging
Remove --staging flag for production certificates.

Mixed content warnings

Moodle may serve some resources over HTTP. Enable HTTPS enforcement in Moodle:Site administration > Security > HTTP security
  • Enable “Use HTTPS for logins”
  • Set “Cookie secure” to Yes

Redirect loops

Check that Moodle’s $CFG->wwwroot matches your actual domain and protocol:
// In ./html/config.php
$CFG->wwwroot = 'https://your-domain.com'; // Must match nginx server_name
After changing SSL configuration, clear your browser cache and Moodle caches from Site administration > Development > Purge all caches

Build docs developers (and LLMs) love