Skip to content

Instantly share code, notes, and snippets.

@pythoninthegrass
Last active July 22, 2025 08:05
Show Gist options
  • Save pythoninthegrass/b127241b67678dd8bf3e297bea992de2 to your computer and use it in GitHub Desktop.
Save pythoninthegrass/b127241b67678dd8bf3e297bea992de2 to your computer and use it in GitHub Desktop.
Generates self-signed certs for local development
# ETC
**/certs/*
# INCLUDE
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
#!/usr/bin/env bash
# SOURCES
# https://github.com/bitnami/containers/blob/main/bitnami/joomla/3/debian-11/rootfs/opt/bitnami/scripts/apache/setup.sh#L25-L44
# shellcheck disable=2317
help() {
cat <<- DESCRIPTION >&2
Generates self-signed certs for local development.
USAGE:
$(basename $0) <base_url>
DESCRIPTION
}
# get the top-level directory
if [ -d ".git" ] && [ $(command -v brew >/dev/null 2>&1; echo $?) -eq 0 ]; then
tld="$(git rev-parse --show-toplevel)"
else
tld="$(dirname "$(readlink -f "$0")")"
fi
# skip prompt if base_url is passed as arg
if [[ "$#" -gt 0 ]]; then
base_url="$1"
else
# prompt for fully qualified domain name
read -r -p $'Enter the fully qualified domain name (FQDN) for the site: [acme.com]\n' base_url
if [ -z "$base_url" ]; then
base_url="acme.com"
fi
fi
# open directory
open_dir() {
distro=$(uname -s)
if [ "$distro" = "Darwin" ]; then
open "${tld}/certs"
elif [ "$distro" = "Linux" ]; then
xdg-open "${tld}/certs" 2>/dev/null
else
echo "Unsupported OS. Please open the certs directory manually."
echo -e "They're found in:\n${tld}/certs"
exit 0
fi
}
# generate self-signed certs
gen_cert() {
# convert '.' to '_' for base_url directory
url_dir="${base_url//./_}"
# hardcoded env vars
priv_key="${tld}/certs/${url_dir}/${base_url}.key"
pub_key="${tld}/certs/${url_dir}/${base_url}.crt"
csr_file="${tld}/certs/${url_dir}/${base_url}.csr"
# subj="/CN=localhost"
subj="/CN=${base_url}"
ext="subjectAltName=DNS:${base_url},DNS:www.${base_url},IP:127.0.0.1"
# if certs exist, ask to skip
if [ -f "$priv_key" ] && [ -f "$pub_key" ]; then
echo "Certs already exist. Skip generating certs (y/N)? [y]"
read -r skip
if [ -z "$skip" ]; then
skip="y"
echo "Skipping..."
fi
case "$skip" in
[yY][eE][sS]|[yY])
return
;;
*)
rm -f "$priv_key" "$pub_key"
;;
esac
fi
# generate certs
if [ ! -f "$priv_key" ] || [ ! -f "$pub_key" ]; then
echo "Generating self-signed certs..."
mkdir -p "${tld}/certs/${url_dir}"
openssl genrsa -out "$priv_key" 4096
openssl req -new -sha256 -out "$csr_file" \
-key "$priv_key" -nodes -subj "$subj"
openssl x509 -req -sha256 -in "$csr_file" \
-signkey "$priv_key" \
-out "$pub_key" \
-days 365 \
-extfile <(echo -n "$ext")
openssl x509 -inform PEM -in "$pub_key" -out "${pub_key%.*}.cert"
rm -f "$csr_file"
fi
echo "Successfully generated self-signed certs! Exiting..."
open_dir
}
main() {
# gen_cert
if [ $# -eq 0 ]; then
gen_cert
else
case "$1" in
-h|--help)
help
;;
*)
echo -e "Invalid argument(s): $*\n"
help
exit 1
;;
esac
fi
}
main "$@"
exit 0
# https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-nginx-in-ubuntu-20-04-1
# /etc/nginx/snippets/ssl-params.conf
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable strict transport security for now. You can uncomment the following
# line if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
@pythoninthegrass
Copy link
Author

git clone [email protected]:b127241b67678dd8bf3e297bea992de2.git gen_cert && cd $_
ln -s $(pwd)/gen_cert.sh ~/.local/bin/gen-cert

@pythoninthegrass
Copy link
Author

Moved .gitignore to global ~/.gitignore because in the year of our lord, 2024, it's still not possible to control gist file order without asciibetical hacks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment