Git Submodules Setup Guide

Git Submodules Setup Guide

Setting up a clean main repo with submodules using temporary bare repositories stored inside .git/ — no sibling folder pollution, zero cleanup drama when real remote URLs arrive.

Target Structure

.
├── .git/
│   ├── local-submodules/         ← temporary bare repos (hidden, never tracked)
│   │   ├── Middlewares.git
│   │   ├── PowerAut0mater.git
│   │   └── Shared.git
├── .gitmodules
├── Middlewares/                  ← submodule
├── PowerAut0mater/               ← submodule
└── Shared/                       ← submodule

Why .git/local-submodules/? The .git/ folder is never tracked by Git. This means the bare repos live completely hidden from your working tree, no sibling folders are created, and when you switch to real remote URLs later there is nothing to clean up except one optional rm -rf.

Step 1 — Initialize the Main Repository

# Navigate into your project root
cd /projects

# Initialize a new Git repository and name the default branch "main"
# -b main → sets the initial branch name (instead of the old default "master")
git init -b main

Step 2 — Create Bare Repos Inside .git/

# Create the hidden folder that will hold all temporary bare repos
# -p → creates parent directories as needed; no error if folder already exists
mkdir -p .git/local-submodules

# Initialize a bare Git repository for each submodule
# --bare → creates a repo with NO working tree (just raw git data)
#          correct format for a repo that will only be pushed to / cloned from
# -b main → sets the default branch name to "main"
git init -b main --bare .git/local-submodules/Middlewares.git
git init -b main --bare .git/local-submodules/PowerAut0mater.git
git init -b main --bare .git/local-submodules/Shared.git

Bare repos need at least one commit before they can be used as submodule sources. Clone each into /tmp, make an empty commit, push back, then clean up:

# Clone the bare repo into /tmp so we have a working tree to commit from
# This creates a temporary normal (non-bare) clone at /tmp/Middlewares
git clone .git/local-submodules/Middlewares.git /tmp/Middlewares

cd /tmp/Middlewares

# Create an empty commit (no files needed) to give the bare repo a valid HEAD
# --allow-empty → normally Git refuses commits with no changes; this flag bypasses that
git commit --allow-empty -m "init"

# Push the empty commit back into the bare repo
# This is what makes the bare repo usable as a submodule source
git push

cd /projects

# Repeat the same process for PowerAut0mater
git clone .git/local-submodules/PowerAut0mater.git /tmp/PowerAut0mater
cd /tmp/PowerAut0mater && git commit --allow-empty -m "init" && git push && cd /projects

# Repeat the same process for Shared
git clone .git/local-submodules/Shared.git /tmp/Shared
cd /tmp/Shared && git commit --allow-empty -m "init" && git push && cd /projects

# Delete all three temporary clones — only needed them to seed the bare repos
# -r → recursive (required for directories)
# -f → force (skip confirmation prompts)
rm -rf /tmp/Middlewares /tmp/PowerAut0mater /tmp/Shared

Step 3 — Add Submodules to the Main Repo

# Register each bare repo as a submodule of the main repo
# git submodule add <url> <folder>
#   <url>    → path to the submodule's git repo (local path or remote URL)
#   <folder> → where it will appear in the working tree
#
# This command does three things automatically:
#   1. Clones the bare repo into the specified folder
#   2. Creates / updates the .gitmodules file with the path <-> url mapping
#   3. Stages both the .gitmodules change and the new submodule folder pointer
#
# ⚠️  IMPORTANT: the URL must explicitly start with ./
#     Git requires local paths to begin with ./ or ../
#     to distinguish them from remote hostnames
#
#     ❌ git submodule add .git/local-submodules/Middlewares.git Middlewares
#        fatal: repo URL: must be absolute or begin with ./|../
#
#     ✅ git submodule add ./.git/local-submodules/Middlewares.git Middlewares
#        Cloning into '...' done.
git submodule add ./.git/local-submodules/Middlewares.git Middlewares
git submodule add ./.git/local-submodules/PowerAut0mater.git PowerAut0mater
git submodule add ./.git/local-submodules/Shared.git Shared

Step 4 — Initial Commit

# Stage everything: .gitmodules + all three submodule folder pointers
git add .

# Commit — this snapshot records which exact commit each submodule is pinned to
git commit -m "Initial commit with submodules"

Step 5 — Verify

# Print the contents of .gitmodules to confirm all three submodules are registered
# .gitmodules is the config file Git uses to track submodule paths and their source URLs
cat .gitmodules

Expected output:

[submodule "Middlewares"]
    path = Middlewares
    url = ./.git/local-submodules/Middlewares.git

[submodule "PowerAut0mater"]
    path = PowerAut0mater
    url = ./.git/local-submodules/PowerAut0mater.git

[submodule "Shared"]
    path = Shared
    url = ./.git/local-submodules/Shared.git

Later — Switching to Real Remote URLs

When you have real remote URLs, run the following from inside the main repo:

Step A — Update the URLs

# Update the URL for each submodule inside .gitmodules
# This only edits the config file — it does NOT yet affect the live local clone
git submodule set-url Middlewares https://github.com/you/Middlewares.git
git submodule set-url PowerAut0mater https://github.com/you/PowerAut0mater.git
git submodule set-url Shared https://github.com/you/Shared.git

Step B — Sync and Update

# Propagate the URL changes from .gitmodules into .git/config
# Without this, Git still uses the OLD URLs internally even though .gitmodules was updated
#
# .gitmodules = "source of truth" (tracked file, shared with the whole team via commits)
# .git/config  = "active runtime config" (local only, NOT tracked by Git)
#
# sync reads .gitmodules and writes the new URLs into .git/config to make them active
git submodule sync

# Stage the updated .gitmodules so the URL change is recorded in history
git add .gitmodules

# Commit the URL change so teammates get the correct URLs when they pull
git commit -m "Update submodule remote URLs"

# Fetch and checkout the correct commit for each submodule from the new remote URLs
# --init      → initializes any submodule not yet set up locally (safe to re-run anytime)
# --recursive → also handles nested submodules (submodules inside submodules)
git submodule update --init --recursive

Step C — Optional Cleanup

# Remove the temporary bare repos — no longer referenced by anything
# All submodules now point to real remote URLs so this folder is dead weight
rm -rf .git/local-submodules

Your final structure is exactly:

.
├── .git
├── .gitmodules
├── Middlewares      (submodule → real remote)
├── PowerAut0mater   (submodule → real remote)
└── Shared           (submodule → real remote)

Quick Reference

StageCommandWhat it does
Init main repogit init -b mainCreates .git/, sets default branch to main
Create bare reposgit init -b main --bare .git/local-submodules/<n>.gitRepo with no working tree — push/clone only
Seed bare repoclone → empty commit → push → rm tempGives bare repo a valid HEAD so it can be used as a source
Add submodulegit submodule add ./<url> <folder>Clones + registers path and url in .gitmodules
Update to real URLgit submodule set-url <n> <url>Edits .gitmodules with the new remote URL
Sync URLsgit submodule syncCopies URLs from .gitmodules into .git/config (makes them active)
Pull from remotesgit submodule update --init --recursiveFetches and checks out correct commit from remote
Cleanup temp reposrm -rf .git/local-submodulesDeletes local bare repos, no longer needed

PFX/PKCS12 Certificate Management Guide

This guide provides comprehensive instructions for managing SSL/TLS certificates in PFX (PKCS12) format, including extraction, verification, and conversion to formats commonly used by web servers like Nginx, Apache, and Traefik.

Prerequisites

  • OpenSSL installed on your system
  • Access to your PFX file
  • Password for the PFX file (if protected)

Understanding PFX Files

What is a PFX file?

PFX (Personal Information Exchange) or PKCS#12 is a binary format that bundles:

  • Private Key – Used to decrypt traffic
  • Certificate – Your domain/server certificate (leaf certificate)
  • Certificate Chain – Intermediate and optionally root CA certificates

Why extract components from PFX?

Many web servers and applications require certificates in PEM format with separate or combined files:

  • Nginx: Requires fullchain.pem and privkey.pem
  • Apache: Requires certificate, private key, and chain as separate files
  • Traefik: Can use either PFX or PEM formats
  • HAProxy: Requires combined certificate + key file
  • Docker containers: Often expect PEM format

Common Use Cases

  1. Setting up SSL/TLS on web servers (Nginx, Apache, Traefik)
  2. Migrating certificates between different platforms
  3. Verifying certificate chain completeness before deployment
  4. Converting Windows-exported certificates to Linux-compatible format
  5. Troubleshooting SSL certificate issues

Working with PFX Files

Setup Environment

First, set your PFX password as an environment variable for convenience:

export PFX_PASSWORD='your_password_here'

Note: For empty passwords, use: export PFX_PASSWORD=''

Check if PFX Contains Full Chain

Before extracting, verify that your PFX file contains the complete certificate chain.

Method 1: Count Certificates

openssl pkcs12 -in star.yourfile.pfx -nodes -nokeys -passin pass:$PFX_PASSWORD | grep -c "BEGIN CERTIFICATE"

Expected Results:

  • 1 = Only your certificate (⚠️ incomplete chain)
  • 2 = Your certificate + 1 intermediate CA (✅ typical)
  • 3+ = Your certificate + multiple intermediates (✅ complete chain)

Method 2: Display Certificate Details

openssl pkcs12 -in star.yourfile.pfx -nodes -nokeys -passin pass:$PFX_PASSWORD -info

This command displays:

  • All certificates with their subject and issuer
  • Certificate validity dates
  • Complete certificate chain hierarchy

Extract Components from PFX

Extract Full Certificate Chain (fullchain.pem)

This creates a file containing your certificate and all intermediate certificates.

openssl pkcs12 -in star.yourfile.pfx -out star.yourfile.pem -nodes -nokeys -passin pass:$PFX_PASSWORD

Flags Explained:

  • -in – Input PFX file
  • -out – Output PEM file
  • -nodes – Don’t encrypt the output (removes passphrase)
  • -nokeys – Export only certificates, not the private key
  • -passin pass:$PFX_PASSWORD – Provide password non-interactively

Output: star.yourfile.pem (fullchain.pem)

Extract Private Key (privkey.pem)

This extracts only the private key from the PFX file.

openssl pkcs12 -in star.yourfile.pfx -out star.yourfile.key -nodes -nocerts -passin pass:$PFX_PASSWORD

Flags Explained:

  • -nocerts – Export only the private key, not certificates
  • -nodes – Output key without encryption

Output: star.yourfile.key (privkey.pem)

⚠️ Security Warning: Protect this file! Set proper permissions:

chmod 600 star.yourfile.key

Extract Everything in One File

If you need certificate + chain + private key in a single file:

openssl pkcs12 -in star.yourfile.pfx -out combined.pem -nodes -passin pass:$PFX_PASSWORD

Use Case: HAProxy, some load balancers

Verification Methods

1. View Certificate Subjects and Issuers

openssl crl2pkcs7 -nocrl -certfile star.yourfile.pem | openssl pkcs7 -print_certs -noout

What to look for:

  • Each certificate’s subject should match the next certificate’s issuer
  • Forms a chain from your certificate to the root CA

Example Output:

subject=CN=*.example.com
issuer=CN=Intermediate CA

subject=CN=Intermediate CA
issuer=CN=Root CA

2. Verify Certificate Chain Integrity

openssl verify -CAfile star.yourfile.pem star.yourfile.pem

Expected Output:

star.yourfile.pem: OK

If verification fails, your chain is incomplete or corrupted.

3. Check Certificate Expiration

openssl x509 -in star.yourfile.pem -noout -dates

Output:

notBefore=Jan  1 00:00:00 2024 GMT
notAfter=Dec 31 23:59:59 2025 GMT

4. Display Certificate Details

openssl x509 -in star.yourfile.pem -text -noout

Shows complete certificate information including:

  • Subject and Issuer
  • Validity period
  • Subject Alternative Names (SANs)
  • Key usage
  • Extensions

5. List All Certificates in Chain

openssl storeutl -certs star.yourfile.pem

Displays each certificate in the chain with its details.

Creating Full Chain Certificates

Scenario 1: PFX Missing Intermediate Certificates

If your PFX only contains the leaf certificate, you need to add the chain manually.

Step 1: Extract components

# Extract certificate
openssl pkcs12 -in star.yourfile.pfx -out cert.pem -nodes -nokeys -clcerts -passin pass:$PFX_PASSWORD

# Extract private key
openssl pkcs12 -in star.yourfile.pfx -out privkey.pem -nodes -nocerts -passin pass:$PFX_PASSWORD

Step 2: Obtain intermediate certificates

Download the chain file from your Certificate Authority (CA):

  • Let’s Encrypt: Included automatically
  • DigiCert, Sectigo, etc.: Available in your CA account
  • Or download from: https://www.example-ca.com/chain.pem

Step 3: Combine certificate with chain

cat cert.pem chain.pem > fullchain.pem

Step 4: Create new PFX with complete chain

openssl pkcs12 -export -out newfile.pfx \
  -inkey privkey.pem \
  -in cert.pem \
  -certfile chain.pem \
  -name "*.example.com" \
  -passout pass:$PFX_PASSWORD

Scenario 2: Already Have Separate Files

If you have certificate and chain as separate files:

# Combine certificate and chain
cat star_asb_bh.crt star_asb_bh-chain.pem > fullchain.pem

# Verify the chain
grep -c "BEGIN CERTIFICATE" fullchain.pem
openssl verify -CAfile fullchain.pem fullchain.pem

Optional: Create PFX from separate files:

openssl pkcs12 -export -out newfile.pfx \
  -inkey star_asb_bh.key \
  -in star_asb_bh.crt \
  -certfile star_asb_bh-chain.pem \
  -name "*.asb.bh" \
  -passout pass:$PFX_PASSWORD

Alternative Scenarios

Using with Nginx

Create the required files:

# Full chain certificate
openssl pkcs12 -in star.yourfile.pfx -out /etc/nginx/ssl/fullchain.pem -nodes -nokeys -passin pass:$PFX_PASSWORD

# Private key
openssl pkcs12 -in star.yourfile.pfx -out /etc/nginx/ssl/privkey.pem -nodes -nocerts -passin pass:$PFX_PASSWORD

# Set permissions
chmod 644 /etc/nginx/ssl/fullchain.pem
chmod 600 /etc/nginx/ssl/privkey.pem

Nginx configuration:

server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
}

Using with Traefik

Option 1: Use PFX directly (if Traefik supports it)

tls:
  stores:
    default:
      defaultCertificate:
        certFile: /path/to/star.yourfile.pfx

Option 2: Convert to PEM format

# Extract both cert and key
openssl pkcs12 -in star.yourfile.pfx -out combined.pem -nodes -passin pass:$PFX_PASSWORD

Traefik configuration:

tls:
  certificates:
    - certFile: /path/to/fullchain.pem
      keyFile: /path/to/privkey.pem

Using with Apache

# Certificate
openssl pkcs12 -in star.yourfile.pfx -out /etc/apache2/ssl/cert.pem -nodes -nokeys -clcerts -passin pass:$PFX_PASSWORD

# Private Key
openssl pkcs12 -in star.yourfile.pfx -out /etc/apache2/ssl/privkey.pem -nodes -nocerts -passin pass:$PFX_PASSWORD

# Chain (intermediate certificates)
openssl pkcs12 -in star.yourfile.pfx -out /etc/apache2/ssl/chain.pem -nodes -nokeys -cacerts -passin pass:$PFX_PASSWORD

Apache configuration:

<VirtualHost *:443>
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/cert.pem
    SSLCertificateKeyFile /etc/apache2/ssl/privkey.pem
    SSLCertificateChainFile /etc/apache2/ssl/chain.pem
</VirtualHost>

Troubleshooting

Error: “wrong tag” or “nested asn1 error”

Cause: Corrupted file, wrong format, or legacy encryption

Solutions:

# Try with legacy provider (OpenSSL 3.x)
openssl pkcs12 -in star.yourfile.pfx -nodes -nokeys -legacy -passin pass:$PFX_PASSWORD

# Try with both providers
openssl pkcs12 -in star.yourfile.pfx -nodes -nokeys -provider legacy -provider default -passin pass:$PFX_PASSWORD

Error: File shows all zeros (0x00)

Check file integrity:

hexdump -C star.yourfile.pfx | head -20

Valid PFX should start with: 30 82 or 30 80 or 30 84

If all zeros: File is corrupted. Re-download or re-export from source.


Error: “MAC verification failed”

Cause: Incorrect password

Solution:

  • Verify your password
  • Try empty password: export PFX_PASSWORD=''
  • Re-export the PFX with known password

Certificate Chain Verification Fails

Check the chain order:

openssl storeutl -certs fullchain.pem

Expected order:

  1. Your domain certificate (leaf)
  2. Intermediate CA certificate(s)
  3. Root CA (optional)

Fix incorrect order:

# Manually reorder certificates in a text editor
# Ensure leaf certificate comes first

Missing Intermediate Certificates

Symptoms:

  • Browser shows “NET::ERR_CERT_AUTHORITY_INVALID”
  • SSL Labs test shows “Chain issues”
  • grep -c "BEGIN CERTIFICATE" returns only 1

Solution: Download intermediate certificate from your CA and combine:

cat your-cert.pem intermediate.pem > fullchain.pem

Quick Reference Commands

# Set password
export PFX_PASSWORD='your_password'

# Check certificate count
openssl pkcs12 -in file.pfx -nodes -nokeys -passin pass:$PFX_PASSWORD | grep -c "BEGIN CERTIFICATE"

# Extract full chain
openssl pkcs12 -in file.pfx -out fullchain.pem -nodes -nokeys -passin pass:$PFX_PASSWORD

# Extract private key
openssl pkcs12 -in file.pfx -out privkey.pem -nodes -nocerts -passin pass:$PFX_PASSWORD

# Verify chain
openssl verify -CAfile fullchain.pem fullchain.pem

# View certificate details
openssl x509 -in fullchain.pem -text -noout

# Check expiration
openssl x509 -in fullchain.pem -noout -dates

# Create PFX from separate files
openssl pkcs12 -export -out new.pfx -inkey privkey.pem -in cert.pem -certfile chain.pem

# Combine certificate files
cat cert.pem chain.pem > fullchain.pem

Security Best Practices

  1. Protect Private Keys

    chmod 600 privkey.pem
    chown root:root privkey.pem
    
  2. Never commit certificates to version control

    # Add to .gitignore
    *.pfx
    *.pem
    *.key
    *.crt
    
  3. Use strong passwords for PFX files

    • Minimum 12 characters
    • Mix of letters, numbers, symbols
  4. Regularly rotate certificates

    • Monitor expiration dates
    • Automate renewal where possible
  5. Store backups securely

    • Encrypted storage
    • Access control
    • Regular backup verification

Additional Resources


License

This documentation is provided as-is for educational and reference purposes.


Last Updated: January 2026

How to Use Let’s Encrypt on Windows Server with IIS

This guide explains how to enable a FREE SSL certificate using Let’s Encrypt on a Windows Server running IIS. Specifically, it addresses the challenges of using wildcard certificates for multiple websites with different domains and subdomains.

Scenario:

You have a Windows Server 2019 with IIS 10, a single IP address, and multiple HTTPS websites hosted with different domain names. For subdomains under the same primary domain (like *.example.com), a wildcard certificate works perfectly. However, complications arise when adding websites from different domains, such as mydomain.com.

Initial Setup Example:

IIS 10 hosts the following sites:

  • ABC Server (Website)
    • abc.api.example.com – HTTPS @ 443
  • ABC Client (Website)
    • abc.example.com – HTTPS @ 443
    • bcd.example.com – HTTPS @ 443
    • cde.example.com – HTTPS @ 443
    • admin.example.com – HTTPS @ 443
  • XYZ App (Website)
    • xyz.example.com – HTTPS @ 443
  • SEQ (Website)
    • seq.mydomain.com – HTTPS @ 443

Managing these SSL certificates with multiple domain names can be tricky, but Let’s Encrypt simplifies the process.

Steps to Enable Let’s Encrypt SSL on IIS

  • Enable IIS and Create the .well-known Folder
    • Follow this guide to create the .well-known directory for SSL validation
      • Create a folder on the C drive named well-known. Inside, create another folder called pki-validation. Example: C:\well-known\pki-validation.
      • Place the required validation file in the pki-validation folder.
      • Open IIS Manager and for each site, right-click and select Add Virtual Directory.
      • In the Alias field, enter .well-known. In the Physical Path field, enter the path to the folder you created, e.g., C:\well-known\pki-validation.
      • Confirm with OK. The folder and files should now be accessible via the web.
  • Set Proper Permissions for the C:\well-known\pki-validation Folder
    • Follow this IIS 403 Forbidden solution:
      • Right-click the .well-known folder and select Properties.
      • Navigate to the Security tab.
      • Click Edit and ensure IIS_IUSRS is listed. If not, click Add
      • In the Enter the object names box, type IIS_IUSRS and click OK.
      • Set Read & execute, List folder contents, and Read permissions for IIS_IUSRS.
  • Validate DNS Entries for Each Domain/Subdomain
    • Use a tool like Google Dig to validate DNS entries for the following domains:
      • abc.api.example.com
      • abc.example.com
      • bcd.example.com
      • cde.example.com
      • admim.example.com
      • xyz.example.com
      • seq.mydomain.com
  • Download and Install win-acme
    • Download win-acme from https://www.win-acme.com.
    • After downloading, unblock the files and extract them to C:\win-acme.
  • Run win-acme to Generate SSL Certificates
    • Navigate to C:\win-acme and run win-acme.exe as Administrator.
    • Follow the prompts to select the appropriate site for which you want to generate the SSL certificate.
    • Once complete, your sites will be secured with Let’s Encrypt SSL certificates.

By following these steps, you can manage multiple websites with different domains and subdomains on a single IIS server with Let’s Encrypt SSL certificates, solving the issues typically associated with wildcard certificates for different domains.

Other Resources