
How to Create a Self-Signed SSL Certificate for Nginx in Ubuntu
Self-signed SSL certificates provide a quick and cost-effective way to enable HTTPS on your Nginx server, particularly useful for development environments, internal applications, or proof-of-concept deployments. While they won’t provide the trust validation of commercial certificates, they encrypt traffic just as effectively and help you test SSL configurations before going live. This guide walks you through creating and implementing self-signed certificates on Ubuntu, covering everything from generation to troubleshooting common issues.
Understanding Self-Signed SSL Certificates
Self-signed certificates are digital certificates that you create and sign yourself, rather than having them validated by a trusted Certificate Authority (CA). The encryption strength remains identical to commercial certificates, but browsers will display security warnings since they can’t verify the certificate’s authenticity through a trusted chain.
Here’s how they compare to other certificate types:
Certificate Type | Cost | Browser Trust | Setup Time | Best Use Case |
---|---|---|---|---|
Self-Signed | Free | Warnings displayed | 5-10 minutes | Development, internal apps |
Let’s Encrypt | Free | Fully trusted | 10-15 minutes | Public websites, production |
Commercial SSL | $50-500/year | Fully trusted | Hours to days | Enterprise, e-commerce |
Prerequisites and Setup Requirements
Before jumping into certificate creation, ensure your Ubuntu system meets these requirements:
- Ubuntu 18.04 or newer (tested on 20.04 and 22.04)
- Nginx installed and running
- OpenSSL package (usually pre-installed)
- Root or sudo access
- Basic understanding of Nginx configuration
Verify OpenSSL installation:
openssl version -a
If you need Nginx, install it quickly:
sudo apt update
sudo apt install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
Step-by-Step Certificate Creation Process
The certificate creation involves generating a private key and then creating the certificate itself. We’ll use a single command approach for efficiency, but I’ll also show the step-by-step method for better understanding.
Method 1: Single Command Generation
Create both the private key and certificate in one shot:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt
You’ll be prompted for certificate details. Here’s what each field means and suggested values:
Country Name (2 letter code) [AU]: US
State or Province Name (full name) [Some-State]: California
Locality Name (eg, city) []: San Francisco
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Your Company
Organizational Unit Name (eg, section) []: IT Department
Common Name (e.g. server FQDN or YOUR name) []: your-domain.com
Email Address []: admin@your-domain.com
The Common Name field is crucial – it should match your domain name or server IP address.
Method 2: Step-by-Step Generation
For better control over the process, generate the key and certificate separately:
# Generate private key
sudo openssl genrsa -out /etc/ssl/private/nginx-selfsigned.key 2048
# Generate certificate signing request
sudo openssl req -new -key /etc/ssl/private/nginx-selfsigned.key \
-out /tmp/nginx-selfsigned.csr
# Generate self-signed certificate
sudo openssl x509 -req -days 365 -in /tmp/nginx-selfsigned.csr \
-signkey /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt
# Clean up CSR file
sudo rm /tmp/nginx-selfsigned.csr
Advanced: Custom Configuration File
For repeated certificate generation or specific requirements, create a configuration file:
sudo nano /tmp/cert.conf
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = US
ST = California
L = San Francisco
O = Your Organization
OU = IT Department
CN = your-domain.com
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = your-domain.com
DNS.2 = www.your-domain.com
DNS.3 = localhost
IP.1 = 192.168.1.100
Generate certificate using the config:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-config /tmp/cert.conf \
-keyout /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt
Configuring Nginx for SSL
Now that we have our certificates, let’s configure Nginx to use them. Create a new server block or modify an existing one:
sudo nano /etc/nginx/sites-available/your-site-ssl
Basic SSL configuration:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name your-domain.com www.your-domain.com;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
# Basic SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
# Document root and other settings
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
try_files $uri $uri/ =404;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
For production-ready SSL configuration with enhanced security:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
# Enhanced SSL Security
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# HSTS (optional for self-signed certs)
add_header Strict-Transport-Security "max-age=63072000" always;
# SSL Session Settings
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
# OCSP stapling (disabled for self-signed)
# ssl_stapling on;
# ssl_stapling_verify on;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
Enable the site and restart Nginx:
sudo ln -s /etc/nginx/sites-available/your-site-ssl /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Real-World Use Cases and Examples
Self-signed certificates shine in several scenarios where commercial certificates might be overkill or impractical:
Development Environment Setup
Perfect for local development where you need to test SSL-dependent features:
# Create development certificate for localhost
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/localhost.key \
-out /etc/ssl/certs/localhost.crt \
-subj "/C=US/ST=Dev/L=Local/O=Development/CN=localhost"
Internal Network Applications
For intranet applications where users can accept certificate warnings:
# Certificate for internal IP
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/internal.key \
-out /etc/ssl/certs/internal.crt \
-subj "/C=US/ST=Internal/L=Network/O=Company/CN=192.168.1.100"
API Testing and Development
When building APIs that require SSL for webhook testing or third-party integrations:
server {
listen 443 ssl;
server_name api.internal.com;
ssl_certificate /etc/ssl/certs/api-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/api-selfsigned.key;
location /api/v1/ {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Performance Considerations and Optimization
Self-signed certificates have the same performance characteristics as commercial certificates, but here are some optimization tips:
Configuration | Impact | Recommended Setting |
---|---|---|
SSL Session Cache | Reduces CPU usage by 50-80% | shared:SSL:10m |
Key Size | 2048 vs 4096 bit performance | 2048 (good security/performance balance) |
HTTP/2 Support | 20-30% faster page loads | Always enable with SSL |
SSL Protocols | Security vs compatibility | TLSv1.2 and TLSv1.3 only |
Optimized SSL configuration for performance:
# Add to your server block for better performance
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_buffer_size 8k;
# Enable HTTP/2
listen 443 ssl http2;
listen [::]:443 ssl http2;
Troubleshooting Common Issues
Here are the most frequent problems you’ll encounter and their solutions:
Certificate and Key Mismatch
Verify your certificate and key match:
# Check certificate
openssl x509 -noout -modulus -in /etc/ssl/certs/nginx-selfsigned.crt | openssl md5
# Check key
openssl rsa -noout -modulus -in /etc/ssl/private/nginx-selfsigned.key | openssl md5
The MD5 hashes should be identical.
Permission Issues
Ensure proper permissions on certificate files:
sudo chmod 644 /etc/ssl/certs/nginx-selfsigned.crt
sudo chmod 600 /etc/ssl/private/nginx-selfsigned.key
sudo chown root:root /etc/ssl/certs/nginx-selfsigned.crt
sudo chown root:root /etc/ssl/private/nginx-selfsigned.key
Nginx Configuration Errors
Common configuration mistakes and fixes:
# Test configuration
sudo nginx -t
# Common error: missing semicolon
# Fix: add semicolon at end of each directive
# Common error: wrong file paths
# Fix: verify files exist and paths are correct
ls -la /etc/ssl/certs/nginx-selfsigned.crt
ls -la /etc/ssl/private/nginx-selfsigned.key
Browser Security Warnings
For development purposes, you can bypass browser warnings:
- Chrome: Type “thisisunsafe” when seeing the warning
- Firefox: Click “Advanced” β “Accept the Risk and Continue”
- For automated testing: Use curl with -k flag:
curl -k https://your-domain.com
Security Best Practices and Limitations
While self-signed certificates provide encryption, follow these security practices:
Certificate Management
- Set reasonable expiration dates (365 days maximum)
- Store private keys securely with 600 permissions
- Use strong key sizes (minimum 2048 bits)
- Regularly rotate certificates
- Keep certificate details consistent
Network Security
# Create stronger Diffie-Hellman parameters
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
# Add to Nginx configuration
ssl_dhparam /etc/nginx/dhparam.pem;
Monitoring and Maintenance
Create a script to check certificate expiration:
#!/bin/bash
# check-ssl-expiry.sh
CERT_FILE="/etc/ssl/certs/nginx-selfsigned.crt"
EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$CERT_FILE" | cut -d= -f2)
EXPIRY_SECONDS=$(date -d "$EXPIRY_DATE" +%s)
CURRENT_SECONDS=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( (EXPIRY_SECONDS - CURRENT_SECONDS) / 86400 ))
echo "Certificate expires in $DAYS_UNTIL_EXPIRY days"
if [ $DAYS_UNTIL_EXPIRY -lt 30 ]; then
echo "WARNING: Certificate expires soon!"
fi
Migration Path to Production Certificates
When you’re ready to move from self-signed to production certificates, here’s your migration strategy:
Let’s Encrypt Migration
Install Certbot and obtain free certificates:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
Commercial Certificate Migration
Replace certificate files while keeping the same Nginx configuration structure:
# Replace these lines in your Nginx config:
ssl_certificate /path/to/commercial/certificate.crt;
ssl_certificate_key /path/to/commercial/private.key;
# Test and reload
sudo nginx -t
sudo systemctl reload nginx
Whether you’re setting up a development environment on a VPS or preparing internal applications on dedicated servers, self-signed SSL certificates provide a solid foundation for encrypted communications. They’re particularly valuable for learning SSL configuration, testing HTTPS-dependent applications, and securing internal network traffic where certificate authority validation isn’t required.
For additional technical documentation and SSL best practices, refer to the official Nginx SSL documentation and OpenSSL documentation.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.