B4J Tutorial [Server] Using Let's Encrypt on Ubuntu VPS

So I have created a few JRDC2 apps. I put them on a VPS as live demo. It is running on Ubuntu 18.04 64 bit. Today I wanted to make the app more secure by enabling SSL certificate. I have chosen to use Let's Encrypt. There are many tutorials here but I am confused with the steps. Especially for people who are not familiar with Linux path and commands.
The steps explained in http://wiki.eclipse.org/Jetty/Howto/Configure_SSL#Generating_Keys_and_Certificates_with_JDK_keytool (updated link: Generating Key Pairs and Certificates) is confusing and I have read many times tried to understand which parts are required. After spending a few hours, I have finally able to put all the puzzles together.

To summarize what I have learned,
I can enable SSL (or https in the URL) in my app without purchasing an SSL certificate (since my app is for demo/testing purpose and not considered critical). Let's Encrypt is a popular choice. I found it is easy to install on Ubuntu. Once installed, my website is now SSL enabled (served by Apache on port 80).

#1 How to: Install Let's Encrypt on Ubuntu Linux VPS to Create SSL Certificates

Downloading and Installing Let's Encrypt
1
. Update the server's packages
Bash:
apt-get update & sudo apt-get upgrade
2. Install the GIT package
Bash:
apt-get install git
3. Download a clone of Let's Encrypt from the GitHub repository to /opt
Bash:
git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
4. Navigate to the new /opt/letsencrypt directory
Bash:
cd /opt/letsencrypt
Creating an SSL Certificate
1
. Run Let's Encrypt (api.puterise.com is my domain)*
Bash:
./letsencrypt-auto certonly --standalone -d api.puterise.com
2. Follow the steps
3. Agree to the Terms of Service
4. If everything worked properly, you should receive a message similar to the following
IMPORTANT NOTES:
- If you lose your account credentials, you can recover them through e-mails sent to [email protected].
- Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/api.puterise.com/fullchain.pem. Your cert will expire on 2021-01-31. To obtain a new version of the certificate in the future, simply run Let's Encrypt again.
- Your account credentials have been saved in your Let's Encrypt configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Let's Encrypt, so making regular backups of this folder is ideal.
- If you like Let's Encrypt, please consider supporting our work by
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le


Now, to make it work on B4J JRDC2 app:

#2 How to use Letsencrypt certificate & private key with Jetty (xkr47/letsencrypt-jetty.sh)

Bash:
root@computer:/etc/letsencrypt/live/api.puterise.com# openssl pkcs12 -export -out keystore.pkcs12 -in fullchain.pem -inkey privkey.pem
Bash:
root@computer:/etc/letsencrypt/live/api.puterise.com# keytool -importkeystore -srckeystore keystore.pkcs12 -srcstoretype PKCS12 -destkeystore keystore.jks
Bash:
root@computer:/etc/letsencrypt/live/api.puterise.com# rm keystore.pkcs12
Enter and verify the source and destination password.

1604094103505.png

Now I can use the keystore file in B4J server code.
B4X:
ssl.SetKeyStorePath("/etc/letsencrypt/live/api.puterise.com", "keystore.jks") 'path to keystore file


Conclusion:
The above steps explained how I can enable SSL in hosted VPS server with certificates already generated by Let's Encrypt.
For local development machine, it is easier to follow the steps to generate the keystore file from Generating Key Pairs and Certificates.

1604094565572.png


B4X:
#If RELEASE
ssl.SetKeyStorePath("/etc/letsencrypt/live/api.puterise.com", "keystore.jks") 'path to keystore file
#Else
ssl.SetKeyStorePath("C:\SSL", "jetty.keystore") 'path to keystore file
#End If
 

yfleury

Active Member
Licensed User
Longtime User
I try this
B4X:
root@vps-7a1ecfb2:/opt/letsencrypt/letsencrypt-auto-source# sudo apt install certbot
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  python-pyicu python3-acme python3-certbot python3-configargparse python3-future python3-josepy python3-mock
  python3-openssl python3-parsedatetime python3-pbr python3-requests-toolbelt python3-rfc3339 python3-setuptools
  python3-tz python3-zope.component python3-zope.event python3-zope.hookable python3-zope.interface
Suggested packages:
...
...
Setting up python3-certbot (0.31.0-1+deb10u1) ...
Setting up certbot (0.31.0-1+deb10u1) ...
Created symlink /etc/systemd/system/timers.target.wants/certbot.timer → /lib/systemd/system/certbot.timer.

and this
B4X:
root@vps-7a1ecfb2:/opt/letsencrypt/letsencrypt-auto-source# ./letsencrypt-auto certonly --standalone -d reseau-municipal.ca
Skipping bootstrap because certbot-auto is deprecated on this system.
Your system is not supported by certbot-auto anymore.
Certbot cannot be installed.
Please visit https://certbot.eff.org/ to check for other alternatives.

list file in folder /opt/letsencrypt/
B4X:
root@vps-7a1ecfb2:/opt/letsencrypt# ls
acme                        certbot-dns-gehirn       CHANGELOG.md             pytest.ini
AUTHORS.md                  certbot-dns-google       CODE_OF_CONDUCT.md       README.rst
certbot                     certbot-dns-linode       CONTRIBUTING.md          SECURITY.md
certbot-apache              certbot-dns-luadns       docker-compose.yml       snap
certbot-ci                  certbot-dns-nsone        Dockerfile-dev           tests
certbot-compatibility-test  certbot-dns-ovh          letsencrypt-auto-source  tools
certbot-dns-cloudflare      certbot-dns-rfc2136      letstest                 tox.cover.py
certbot-dns-digitalocean    certbot-dns-route53      LICENSE.txt              tox.ini
certbot-dns-dnsimple        certbot-dns-sakuracloud  linter_plugin.py         windows-installer
certbot-dns-dnsmadeeasy     certbot-nginx            mypy.ini

I can't run b4j webserver in secure
 
Last edited:

aeric

Expert
Licensed User
Longtime User
Snap is the recommended way to install Certbot but I didn't get it to install previously (due to my VM doesn't support Snap). I am bias and dislike Snap. However, you may try and see if it works for you.

Alternative way is to use Pip. I haven't tried it but if Snap doesn't work for you or you want to skip it, give it a try.
https://certbot.eff.org/instructions?ws=other&os=pip

I am not familiar with Docker but you can give it a try too if all the ways above failed.
 

aeric

Expert
Licensed User
Longtime User

Installation on Debian Linux​

1. Install Certbot
Update package index:
sudo apt-get update
For Nginx:
sudo apt-get install certbot python3-certbot-nginx
For Apache:
sudo apt-get install certbot python3-certbot-apache
Standalone (No Web Server Plugin):
sudo apt-get install certbot -y
(You can replace apt-get with apt on Ubuntu)

2. Fetching a Certificate (producing fullchain.pem, privkey.pem, cert.pem, chain.pem)
For Nginx:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
For Apache:
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
or skip user prompts by providing your email and agreeing to the Terms of Service directly in the flag.
For Nginx:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com --email [email protected] --agree-tos --non-interactive
Standalone (No Web Server Plugin):
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com --email [email protected] --agree-tos --non-interactive
Run Certbot in standalone mode. It will instantly spin up a temporary server on Port 80, grab the certificates, and close itself out.
Note: Make sure your firewall allows incoming traffic on Port 80 for this step to succeed.

Certbot downloads the actual certificates to /etc/letsencrypt/archive/yourdomain.com/. However, it creates symbolic links pointing to the newest versions in /etc/letsencrypt/live/yourdomain.com/

3. Automating Renewal
Bash:
sudo certbot renew --dry-run
Certbot never overwrites old certificates. Instead, it downloads the new files into /etc/letsencrypt/archive/yourdomain.com/ and increments the number at the end of the filename.
  • Old files: cert1.pem, priv1.pem, fullchain1.pem
  • New files: cert2.pem, priv2.pem, fullchain2.pem
3.1. Updated Symbolic Links
Once the new files are downloaded, Certbot automatically updates the symbolic links inside /etc/letsencrypt/live/yourdomain.com/.
  • The links (fullchain.pem, privkey.pem, etc.) are seamlessly shifted to point to the newest archived files (e.g., fullchain2.pem).
  • Because Nginx points to the live directory, you do not need to update your Nginx configuration file.
3.2. Static Configuration Files
The global security files (options-ssl-nginx.conf and ssl-dhparams.pem) are not redownloaded or changed during a renewal. They remain exactly as they were.

3.3. Automatic Nginx Reload
Certbot will automatically reload Nginx after a successful renewal so the web server can read the new files. It does this without dropping active user connections.

4. Export Let's Encrypt certificates into a password-protected PKCS#12 keystore format (.p12) for B4J Server
Bash:
openssl pkcs12 -export -out /home/keystore.p12 -in /etc/letsencrypt/live/yourdomain.com/fullchain.pem -inkey /etc/letsencrypt/live/yourdomain.com/privkey.pem -name jetty
or without password prompt
Bash:
sudo openssl pkcs12 -export -out /home/keystore.p12 -in /etc/letsencrypt/live/yourdomain.com -inkey /etc/letsencrypt/live/yourdomain.com -name jetty -passout pass:your_keystore_password

5. ConfigureSSL in jServer
B4X:
Dim ssl As SslConfiguration
ssl.Initialize
ssl.SetKeyStorePath("/etc/letsencrypt/live/yourdomain.com", "keystore.p12")
ssl.KeyStorePassword = "your_keystore_password"
srvr.SetSslConfiguration(ssl, 51041)

6. Automated Renewal Script
Single server:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/update-b4j.sh << 'EOF'
#!/bin/bash

# 1. Path variables (Adjust these to match your actual server setup!)
JAR_PATH="/home/myb4japp/your_app_name.jar"
LOG_PATH="/home/myb4japp/nohup.out"
KEYSTORE_PATH="/home/keystore.p12"
DOMAIN="yourdomain.com"
PASSWORD="your_secret_password"

# 2. Re-generate the PKCS12 file with the newly renewed certificates
openssl pkcs12 -export -out "$KEYSTORE_PATH" \
  -in /etc/letsencrypt/live/"$DOMAIN"/fullchain.pem \
  -inkey /etc/letsencrypt/live/"$DOMAIN"/privkey.pem \
  -name b4j -passout pass:"$PASSWORD"

# 3. Find and kill the existing running B4J process safely
# This looks for the unique .jar filename to avoid killing other java apps
PID=$(pgrep -f "java.*$JAR_PATH")

if [ -n "$PID" ]; then
    echo "Stopping existing B4J process ($PID)..."
    kill "$PID"
    sleep 3 # Give it a few seconds to release the ports safely
else
    echo "No running B4J process found to stop."
fi

# 4. Restart the B4J app in the background using nohup
echo "Starting B4J application via nohup..."
cd "$(dirname "$JAR_PATH")"
nohup java -jar "$JAR_PATH" > "$LOG_PATH" 2>&1 &

echo "B4J update and restart completed successfully!"
EOF
Remember to make the script executable.
Set execution permissions:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/update-b4j.sh

📋 Important Considerations for nohup
  • File paths must be absolute: Always use full explicit paths (like /home/myb4japp/your_app_name.jar) in scripts run by Certbot, as cron environments do not know your user's relative paths. [1]
  • Temporary Down Time: Your server will experience roughly 3 seconds of downtime while the old Java process closes and the new one spins up to read the fresh keystore file.
  • Working Directory: The script includes a cd command right before launching nohup. This ensures that any relative paths inside your B4J app code (like checking for local .db or asset folders) do not break.

This script is executed automatically by Certbot’s built-in renewal system via a system timer or cron job. You do not need to schedule or trigger the script yourself.

Test the script: You can manually run the script once right now to verify it handles the keystore generation and successfully restarts your app without errors:
Bash:
sudo /etc/letsencrypt/renewal-hooks/deploy/update-b4j.sh

Verify it is Set Up Correctly
You can test the entire pipeline right now—including checking if Certbot discovers your script and executes it without errors—by performing a dry run simulation.
Bash:
sudo certbot renew --dry-run
If everything is configured properly, the logs will simulate the renewal process and output a section at the bottom similar to this:
Bash:
Dry run count: 1
Simulating renewal of an existing certificate for yourdomain.com
Running deploy-hook command: /etc/letsencrypt/renewal-hooks/deploy/update-b4j.sh
 
Last edited:

aeric

Expert
Licensed User
Longtime User
If having multiple B4J Servers sharing the same keystore, modify the script to loop through and restart every single one of them when the certificate renews.
update-b4j.sh:
#!/bin/bash

# 1. Keystore Configuration
KEYSTORE_PATH="/home/keystore.p12"
DOMAIN="yourdomain.com"
PASSWORD="your_secret_password"

# 2. Define all your B4J Jar files here (Add as many as you need)
JAR_FILES=(
  "/home/server1/app1.jar"
  "/home/server2/app2.jar"
  "/home/server3/app3.jar"
)

# 3. Find the correct domain directory (handles -0001, -0002, etc.)
REAL_DOMAIN_DIR=$(ls -td /etc/letsencrypt/live/"$DOMAIN"* | head -n 1)

# 4. Re-generate the shared PKCS12 file once using the detected directory
openssl pkcs12 -export -out "$KEYSTORE_PATH" \
    -in "$REAL_DOMAIN_DIR/fullchain.pem" \
    -inkey "$REAL_DOMAIN_DIR/privkey.pem" \
    -name b4j -passout pass:"$PASSWORD"

echo "Shared keystore updated successfully."

# 5. Loop through and restart each B4J application
for JAR_PATH in "${JAR_FILES[@]}"; do
    echo  "----------------------------------------"
    echo "Processing: $JAR_PATH"
   
    # Extract the directory path and the raw file name for logs
    APP_DIR=$(dirname "$JAR_PATH")
    JAR_NAME=$(basename "$JAR_PATH" .jar)
    LOG_PATH="$APP_DIR/${JAR_NAME}_nohup.out"
   
    # Find the specific PID for this exact jar file
    PID=$(pgrep -f "java.*$JAR_PATH")
   
    if [ -n "$PID" ]; then
        echo "Stopping existing process ($PID)..."
        kill "$PID"
        sleep 2 # Brief pause to release ports safely
    else
        echo "No running process found for this app."
    fi
   
    # Restart the specific B4J app in its own directory
    echo "Relaunching via nohup..."
    cd "$APP_DIR"
    nohup java -jar "$JAR_PATH" > "$LOG_PATH" 2>&1 &
   
    echo "$JAR_NAME restarted."
done

echo "----------------------------------------"
echo "All B4J servers have been updated and restarted!"

7. Auto start script after reboot
/home/startup.sh:
sudo tee /home/startup.sh << 'EOF'
#!/bin/bash
# Define all your B4J Jar files using absolute paths
JAR_FILES=(
  "/home/server1/app1.jar"
  "/home/server2/app2.jar"
  "/home/server3/app3.jar"
)
# Loop through and start apps ONLY if they are not already running
for JAR_PATH in "${JAR_FILES[@]}"; do
    APP_DIR=$(dirname "$JAR_PATH")
    JAR_NAME=$(basename "$JAR_PATH" .jar)
    LOG_PATH="$APP_DIR/${JAR_NAME}_nohup.out"
 
    # Check if the process is already running
    if pgrep -f "java.*$JAR_PATH" > /dev/null; then
        echo "$JAR_NAME is already running. Skipping."
    else
        echo "Starting $JAR_NAME..."
        cd "$APP_DIR" || continue
        nohup java -jar "$JAR_PATH" > "$LOG_PATH" 2>&1 < /dev/null &
    fi
done
EOF

Bash:
sudo crontab -e
Add this line:
Bash:
@reboot startup.sh
 
Last edited:
Top