Self-Signed Certificates for Internal Services

Self-signed certificates secure internal applications and services without relying on external Certificate Authorities. They're ideal for development, testing, internal APIs, and microservices requiring encryption. This guide covers creating a private CA, issuing certificates, managing trust stores, and Java keystore configuration.

Table of Contents

Prerequisites

Before creating self-signed certificates, ensure you have:

  • OpenSSL installed
  • Root or sudo access
  • Internal domain names or IP addresses
  • Text editor for configuration
  • Java development kit (for keystore examples)

Creating a Private Certificate Authority

Create your own CA for signing internal certificates. This CA is trusted only within your organization.

Create CA directory structure:

mkdir -p ~/ca/{root,intermediate,certs,private,csr}
cd ~/ca

# Set proper permissions
chmod 700 private
chmod 755 certs

Generate CA private key (4096-bit RSA):

openssl genrsa -aes256 -out private/ca.key 4096

When prompted, enter a strong passphrase to protect the CA private key.

Create CA configuration file at ca.conf:

cat > ca.conf <<EOF
[ ca ]
default_ca = CA_default

[ CA_default ]
dir              = ./
new_certs_dir    = certs
crl_dir          = ./
database         = index.txt
serial           = serial

[ policy_loose ]
countryName             = optional
stateOrProvinceName      = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
default_bits        = 4096
string_mask         = utf8only
default_md          = sha256
x509_extensions     = v3_ca
distinguished_name  = req_distinguished_name

[ req_distinguished_name ]
countryName_default             = US
stateOrProvinceName_default     = State
localityName_default            = City
organizationName_default        = Organization
organizationalUnitName_default  = Department
commonName                      = Common Name
emailAddress                    = Email Address

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true

[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
authorityKeyIdentifier=keyid:always

[ ocsp ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning
EOF

Generate CA certificate (self-signed, 10-year validity):

openssl req -config ca.conf \
  -key private/ca.key \
  -new -x509 -days 3650 \
  -sha256 -extensions v3_ca \
  -out root-ca.crt \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=Internal-CA"

Verify CA certificate:

openssl x509 -noout -text -in root-ca.crt

Initialize certificate database:

touch index.txt
echo 1000 > serial

Generating Server Certificates

Generate certificates for your internal services.

Create server certificate signing request (CSR):

# For a specific hostname
openssl req -config ca.conf \
  -key private/server.key \
  -new -sha256 \
  -out csr/server.csr \
  -keyout private/server.key \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=api.internal"

Or create a separate key first:

openssl genrsa -out private/server.key 2048

Create CSR for existing key:

openssl req -new \
  -key private/server.key \
  -out csr/server.csr \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=api.internal"

For certificates with multiple Subject Alternative Names (SANs), create extension file:

cat > csr/server-san.ext <<EOF
subjectAltName = DNS:api.internal,DNS:api,DNS:api.example.com,IP:192.168.1.10
EOF

Sign the server certificate with CA:

openssl ca -config ca.conf \
  -extensions server_cert \
  -days 730 \
  -notext -md sha256 \
  -in csr/server.csr \
  -out certs/server.crt

When prompted, confirm with "y" twice.

Sign with SAN extension:

openssl x509 -req \
  -in csr/server.csr \
  -CA root-ca.crt \
  -CAkey private/ca.key \
  -CAcreateserial \
  -out certs/server.crt \
  -days 730 \
  -sha256 \
  -extfile csr/server-san.ext

Verify server certificate:

openssl x509 -noout -text -in certs/server.crt
openssl verify -CAfile root-ca.crt certs/server.crt

Creating Client Certificates

Generate certificates for client authentication.

Create client CSR:

openssl req -new \
  -key private/client.key \
  -keyout private/client.key \
  -out csr/client.csr \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=client-app"

Create extension for client usage:

cat > csr/client.ext <<EOF
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
EOF

Sign client certificate:

openssl x509 -req \
  -in csr/client.csr \
  -CA root-ca.crt \
  -CAkey private/ca.key \
  -CAcreateserial \
  -out certs/client.crt \
  -days 730 \
  -sha256 \
  -extfile csr/client.ext

Create PKCS12 keystore (for browser/application import):

openssl pkcs12 -export \
  -in certs/client.crt \
  -inkey private/client.key \
  -out certs/client.p12 \
  -name "client-app" \
  -passout pass:clientpassword

Trust Store Configuration

Make the CA certificate trusted on systems.

Linux - Add to system CA store:

# Copy CA certificate
sudo cp root-ca.crt /usr/local/share/ca-certificates/

# Update CA store
sudo update-ca-certificates

# Verify
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server.crt

Export CA certificate in PEM format:

openssl x509 -in root-ca.crt -out root-ca.pem

Firefox - Manual import:

  1. Open Preferences > Privacy & Security > Certificates
  2. Click "View Certificates"
  3. Import Authority tab
  4. Select root-ca.crt
  5. Trust for website identification

Chrome - Ubuntu:

sudo cp root-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

Chrome uses system CA store on most platforms.

Python - Trust CA certificate:

import ssl
import certifi

# Create custom SSL context
context = ssl.create_default_context()
context.load_verify_locations('/path/to/root-ca.crt')

# Use in requests
import requests
response = requests.get('https://api.internal', verify='/path/to/root-ca.crt')

Node.js - Trust CA certificate:

export NODE_EXTRA_CA_CERTS=/path/to/root-ca.crt

Or in code:

const https = require('https');
const fs = require('fs');

const agent = new https.Agent({
  ca: fs.readFileSync('/path/to/root-ca.crt')
});

https.get('https://api.internal', { agent }, (res) => {
  console.log(res.statusCode);
});

Java Keystore Setup

Create Java keystores for internal certificates.

Import CA certificate:

keytool -import -alias internal-ca \
  -file root-ca.crt \
  -keystore truststore.jks \
  -storepass trustpassword \
  -noprompt

Create server keystore with certificate:

keytool -import -alias server \
  -file certs/server.crt \
  -keystore server.jks \
  -storepass keystorepassword \
  -noprompt

Or create from PKCS12:

openssl pkcs12 -export \
  -in certs/server.crt \
  -inkey private/server.key \
  -out server.p12 \
  -name server \
  -passout pass:keystorepassword

keytool -importkeystore \
  -srckeystore server.p12 \
  -srcstoretype PKCS12 \
  -srcstorepass keystorepassword \
  -destkeystore server.jks \
  -deststoretype JKS \
  -deststorepass keystorepassword

Import client certificate:

keytool -import -alias client \
  -file certs/client.crt \
  -keystore client.jks \
  -storepass keystorepassword \
  -noprompt

Verify keystore contents:

keytool -list -v -keystore server.jks -storepass keystorepassword

Java application configuration:

# Via system properties
java -Djavax.net.ssl.trustStore=truststore.jks \
  -Djavax.net.ssl.trustStorePassword=trustpassword \
  -Djavax.net.ssl.keyStore=server.jks \
  -Djavax.net.ssl.keyStorePassword=keystorepassword \
  MyApp

# Via environment
export JAVA_OPTS="-Djavax.net.ssl.trustStore=/path/to/truststore.jks"

Nginx Configuration

Configure Nginx with self-signed certificates:

server {
    listen 443 ssl http2;
    server_name api.internal;
    
    # Self-signed certificate and key
    ssl_certificate /opt/certs/server.crt;
    ssl_certificate_key /opt/private/server.key;
    
    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    
    # Client certificate authentication (optional)
    ssl_client_certificate /opt/certs/client.crt;
    ssl_verify_client optional;
    
    location / {
        proxy_pass http://backend:8080;
    }
}

Test connection:

curl --cacert /opt/certs/root-ca.crt https://api.internal

With client certificate:

curl --cacert /opt/certs/root-ca.crt \
  --cert /opt/certs/client.crt \
  --key /opt/private/client.key \
  https://api.internal

Apache Configuration

Configure Apache with self-signed certificates:

Listen 443 ssl

<VirtualHost *:443>
    ServerName api.internal
    
    SSLEngine on
    SSLCertificateFile /opt/certs/server.crt
    SSLCertificateKeyFile /opt/private/server.key
    
    SSLProtocol TLSv1.2 TLSv1.3
    SSLCipherSuite HIGH:!aNULL:!MD5
    
    # Client certificate verification
    SSLVerifyClient optional
    SSLVerifyDepth 1
    SSLCACertificatePath /opt/certs
    
    ProxyPreserveHost On
    ProxyPass / http://backend:8080/
    ProxyPassReverse / http://backend:8080/
</VirtualHost>

Certificate Renewal

Renew self-signed certificates when expiring.

Check certificate expiration:

openssl x509 -noout -enddate -in certs/server.crt

Renew server certificate:

# New CSR
openssl req -new \
  -key private/server.key \
  -out csr/server-new.csr \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=api.internal"

# Sign with updated expiration
openssl x509 -req \
  -in csr/server-new.csr \
  -CA root-ca.crt \
  -CAkey private/ca.key \
  -CAcreateserial \
  -out certs/server-new.crt \
  -days 730 \
  -sha256

# Backup and replace
cp certs/server.crt certs/server-old.crt
cp certs/server-new.crt certs/server.crt

# Reload services
systemctl reload nginx

Troubleshooting

Certificate trust issues:

# Verify certificate chain
openssl verify -CAfile root-ca.crt certs/server.crt

# Check certificate details
openssl x509 -in certs/server.crt -text -noout

# Test with curl
curl -v --cacert root-ca.crt https://api.internal

Hostname mismatch warnings:

# Check certificate CN and SANs
openssl x509 -in certs/server.crt -text | grep -A2 "Subject:\|Alternative"

# Regenerate with correct CN/SAN

Java keystore issues:

# List keystore
keytool -list -keystore truststore.jks

# View certificate in keystore
keytool -list -v -keystore truststore.jks -alias internal-ca

# Remove certificate
keytool -delete -keystore truststore.jks -alias internal-ca

Conclusion

Self-signed certificates provide encryption for internal services without external CA costs. This guide covered creating a private CA, issuing server and client certificates, configuring trust stores across operating systems, Java keystore setup, and web server configuration. For production internal services, maintain secure backups of CA keys, implement certificate rotation procedures, monitor expiration dates, and establish policies for certificate renewal. Self-signed certificates excel for development, testing, and internal-only services requiring encryption.