[GH-ISSUE #779] nginx proxy manager in fron of mailcow #656

Closed
opened 2026-02-26 06:33:52 +03:00 by kerem · 13 comments
Owner

Originally created by @cjohn001 on GitHub (Dec 17, 2020).
Original GitHub issue: https://github.com/NginxProxyManager/nginx-proxy-manager/issues/779

Hello together,
I am currently trying to figure out if I can use nginx proxy manager in fron of my mailcow installation. The question I have is related to ssl certificates generated with letsencrypt. In case I use proxy manager to obtain ssl certificates I would need to be able to execute a posthook script each time the certificates are updated. Is this possible and how could I do it?

I need to provide the ssl certificates to my mailcow installation (which uses the certificates with postfix,dovecat and internal nginx containers) and after I copied the certificates I need to run a script to restart the relevant mailcow containers.

would be great if someone could show me directions

Originally created by @cjohn001 on GitHub (Dec 17, 2020). Original GitHub issue: https://github.com/NginxProxyManager/nginx-proxy-manager/issues/779 Hello together, I am currently trying to figure out if I can use nginx proxy manager in fron of my mailcow installation. The question I have is related to ssl certificates generated with letsencrypt. In case I use proxy manager to obtain ssl certificates I would need to be able to execute a posthook script each time the certificates are updated. Is this possible and how could I do it? I need to provide the ssl certificates to my mailcow installation (which uses the certificates with postfix,dovecat and internal nginx containers) and after I copied the certificates I need to run a script to restart the relevant mailcow containers. would be great if someone could show me directions
kerem 2026-02-26 06:33:52 +03:00
Author
Owner

@3xitsharp commented on GitHub (Dec 27, 2020):

Duplicate issue and possible workaround https://github.com/jc21/nginx-proxy-manager/issues/690

To answer your question. NPM provides acme hook directories in its installation path. Simply scp the certificate over to your mailcow host and restart mailcow containers.

./letsencrypt/renewal-hooks/pre
./letsencrypt/renewal-hooks/post
./letsencrypt/renewal-hooks/deploy

For my workaround i've completely disabled HTTP validation in NPM since i only use DNS validation anyways.

  1. mount a custom /etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf into the NPM container to prevent the "duplicate location" error.
# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
#location ^~ /.well-known/acme-challenge/ {
#        # Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure
#        # we need to open up access by turning off auth and IP ACL for this location.
#        auth_basic off;
#        allow all;
#
#        # Set correct content type. According to this:
#        # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
#        # Current specification requires "text/plain" or no content header at all.
#        # It seems that "text/plain" is a safe option.
#        default_type "text/plain";
#
#        # This directory must be the same as in /etc/letsencrypt/cli.ini
#        # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter
#        # there to "webroot".
#        # Do NOT use alias, use root! Target directory is located here:
#        # /var/www/common/letsencrypt/.well-known/acme-challenge/
#        root /data/letsencrypt-acme-challenge;
#}

# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
        return 404;
}
  1. Add Custom location to the mailcow proxy host config

Define location
^~ /.well-known/acme-challenge/

Forward Hostname / IP
mailcow.ip.address/.well-known/acme-challenge/

Scheme / Forward Port
http / 80

  1. disable HTTP verification in mailcow.conf SKIP_HTTP_VERIFICATION=y and most likely SKIP_IP_CHECK=y
  2. restart mailcow-acme container docker-compose restart acme-mailcow and watch logs for successful certificate generation docker-compose logs --tail=200 -f acme-mailcow
<!-- gh-comment-id:751507899 --> @3xitsharp commented on GitHub (Dec 27, 2020): Duplicate issue and possible workaround https://github.com/jc21/nginx-proxy-manager/issues/690 To answer your question. NPM provides acme hook directories in its installation path. Simply scp the certificate over to your mailcow host and restart mailcow containers. ``` ./letsencrypt/renewal-hooks/pre ./letsencrypt/renewal-hooks/post ./letsencrypt/renewal-hooks/deploy ``` For my workaround i've completely disabled HTTP validation in NPM since i only use DNS validation anyways. 1. mount a custom `/etc/nginx/conf.d/include/letsencrypt-acme-challenge.conf` into the NPM container to prevent the "duplicate location" error. ``` # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) # We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel # other regex checks, because in our other config files have regex rule that denies access to files with dotted names. #location ^~ /.well-known/acme-challenge/ { # # Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure # # we need to open up access by turning off auth and IP ACL for this location. # auth_basic off; # allow all; # # # Set correct content type. According to this: # # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29 # # Current specification requires "text/plain" or no content header at all. # # It seems that "text/plain" is a safe option. # default_type "text/plain"; # # # This directory must be the same as in /etc/letsencrypt/cli.ini # # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter # # there to "webroot". # # Do NOT use alias, use root! Target directory is located here: # # /var/www/common/letsencrypt/.well-known/acme-challenge/ # root /data/letsencrypt-acme-challenge; #} # Hide /acme-challenge subdirectory and return 404 on all requests. # It is somewhat more secure than letting Nginx return 403. # Ending slash is important! location = /.well-known/acme-challenge/ { return 404; } ``` 2. Add Custom location to the mailcow proxy host config Define location `^~ /.well-known/acme-challenge/` Forward Hostname / IP `mailcow.ip.address/.well-known/acme-challenge/` Scheme / Forward Port `http / 80` 3. disable HTTP verification in mailcow.conf `SKIP_HTTP_VERIFICATION=y` and most likely `SKIP_IP_CHECK=y` 4. restart mailcow-acme container `docker-compose restart acme-mailcow` and watch logs for successful certificate generation `docker-compose logs --tail=200 -f acme-mailcow`
Author
Owner

@meinradr commented on GitHub (Aug 24, 2021):

I do have the same issue. Could someone please explain what acme renewal hooks are and how to use them. It is also not clear which certificate I have to copy since they all appear to have arbitrary names.

<!-- gh-comment-id:904937619 --> @meinradr commented on GitHub (Aug 24, 2021): I do have the same issue. Could someone please explain what acme renewal hooks are and how to use them. It is also not clear which certificate I have to copy since they all appear to have arbitrary names.
Author
Owner

@LucaOverflow commented on GitHub (Aug 31, 2021):

I do have the same issue. Could someone please explain what acme renewal hooks are and how to use them. It is also not clear which certificate I have to copy since they all appear to have arbitrary names.

NPM automatically calls bash scripts in those mentioned hook directorys. So you can write a script that copies your certificates to another server via scp. One hint: NPM will call the scripts in those directories for every certificate and as far as I know you cannot configure it otherwise. So either use the deploy hook directory since you can use the environment variables $RENEWED_DOMAINS and $RENEWED_LINEAGE to do domain/certificate specific things or edit the /data/letsencrypt/renewal/npm-x.conf and add a post_hook line linking to a script file in a different location.

The certificate number can be seen in the UI. Just go to the SSL Certificates site and click the tree dots of your certificate. The popup will show you the number.

<!-- gh-comment-id:909356841 --> @LucaOverflow commented on GitHub (Aug 31, 2021): > I do have the same issue. Could someone please explain what acme renewal hooks are and how to use them. It is also not clear which certificate I have to copy since they all appear to have arbitrary names. NPM automatically calls bash scripts in those mentioned hook directorys. So you can write a script that copies your certificates to another server via scp. One hint: NPM will call the scripts in those directories for every certificate and as far as I know you cannot configure it otherwise. So either use the deploy hook directory since you can use the environment variables `$RENEWED_DOMAINS` and `$RENEWED_LINEAGE` to do domain/certificate specific things or edit the /data/letsencrypt/renewal/npm-x.conf and add a `post_hook` line linking to a script file in a different location. The certificate number can be seen in the UI. Just go to the SSL Certificates site and click the tree dots of your certificate. The popup will show you the number.
Author
Owner

@lriley2020 commented on GitHub (Jun 1, 2022):

I'm also having the same issue: I've got as far as copying the certificates over with the renew hook, but I'm not sure how to restart the mailcow containers afterwards, as the renew hook script gets executed in the nginx proxy manager containter, rather than the host. Does anyone know how I can restart the containers as described here from the nginx proxy manager container?

<!-- gh-comment-id:1143781545 --> @lriley2020 commented on GitHub (Jun 1, 2022): I'm also having the same issue: I've got as far as copying the certificates over with the renew hook, but I'm not sure how to restart the mailcow containers afterwards, as the renew hook script gets executed in the nginx proxy manager containter, rather than the host. Does anyone know how I can restart the containers as described [here](https://mailcow.github.io/mailcow-dockerized-docs/post_installation/firststeps-rp/#optional-post-hook-script-for-non-mailcow-acme-clients) from the nginx proxy manager container?
Author
Owner

@LucaOverflow commented on GitHub (Jun 1, 2022):

I'm also having the same issue: I've got as far as copying the certificates over with the renew hook, but I'm not sure how to restart the mailcow containers afterwards, as the renew hook script gets executed in the nginx proxy manager containter, rather than the host. Does anyone know how I can restart the containers as described here from the nginx proxy manager container?

Yes, but note, that this should only be run in a locked down environment, since it requires root access over ssh. I'm sure there are better solutions out there, but this is my solution:

#!/bin/bash
#echo $RENEWED_DOMAINS
if [[ $RENEWED_DOMAINS == *"your.domain.tld"* ]]
then
  echo "Mailcow Certificate Copy started"

  # find latest fullchain file
  unset -v latest_fullchain
  for file in /config/letsencrypt/archive/npm-XX/fullchain?.pem; do
    [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file
  done
  echo "Latest fullchain found: " $latest_fullchain

  # find latest privkey file
  unset -v latest_privkey
  for file in /config/letsencrypt/archive/npm-XX/privkey?.pem; do
    [[ $file -nt $latest_privkey ]] && latest_privkey=$file
  done
  echo "Latest privkey found: " $latest_privkey

  echo "Updating apk repositories"
  apk update
  echo "Installing openssh and sshpass"
  apk add openssh sshpass
  echo "Copying fullchain to Mailcow"
  sshpass -p "password" scp -o "StrictHostKeyChecking no" $latest_fullchain root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/cert.pem
  echo "Copying privkey to Mailcow"
  sshpass -p "password" scp -o "StrictHostKeyChecking no"  $latest_privkey root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/key.pem
  echo "Start ssl_restart script on Mailcow VM"
  sshpass -p "password" ssh -o "StrictHostKeyChecking no" -l root MAILCOW_IP "bash /opt/mailcow-dockerized/ssl_restart.sh"
  echo "Mailcow Certificate Copy finished"
fi

I'm using sshpass which allows me to run a script on the mailcow container, that restarts the services.

Sidenote:
For this script to work it needs to be placed in the deploy hooks directory

<!-- gh-comment-id:1143850329 --> @LucaOverflow commented on GitHub (Jun 1, 2022): > I'm also having the same issue: I've got as far as copying the certificates over with the renew hook, but I'm not sure how to restart the mailcow containers afterwards, as the renew hook script gets executed in the nginx proxy manager containter, rather than the host. Does anyone know how I can restart the containers as described [here](https://mailcow.github.io/mailcow-dockerized-docs/post_installation/firststeps-rp/#optional-post-hook-script-for-non-mailcow-acme-clients) from the nginx proxy manager container? Yes, but note, that this should only be run in a locked down environment, since it requires root access over ssh. I'm sure there are better solutions out there, but this is my solution: ```console #!/bin/bash #echo $RENEWED_DOMAINS if [[ $RENEWED_DOMAINS == *"your.domain.tld"* ]] then echo "Mailcow Certificate Copy started" # find latest fullchain file unset -v latest_fullchain for file in /config/letsencrypt/archive/npm-XX/fullchain?.pem; do [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file done echo "Latest fullchain found: " $latest_fullchain # find latest privkey file unset -v latest_privkey for file in /config/letsencrypt/archive/npm-XX/privkey?.pem; do [[ $file -nt $latest_privkey ]] && latest_privkey=$file done echo "Latest privkey found: " $latest_privkey echo "Updating apk repositories" apk update echo "Installing openssh and sshpass" apk add openssh sshpass echo "Copying fullchain to Mailcow" sshpass -p "password" scp -o "StrictHostKeyChecking no" $latest_fullchain root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/cert.pem echo "Copying privkey to Mailcow" sshpass -p "password" scp -o "StrictHostKeyChecking no" $latest_privkey root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/key.pem echo "Start ssl_restart script on Mailcow VM" sshpass -p "password" ssh -o "StrictHostKeyChecking no" -l root MAILCOW_IP "bash /opt/mailcow-dockerized/ssl_restart.sh" echo "Mailcow Certificate Copy finished" fi ``` I'm using sshpass which allows me to run a script on the mailcow container, that restarts the services. Sidenote: For this script to work it needs to be placed in the deploy hooks directory
Author
Owner

@lriley2020 commented on GitHub (Jun 1, 2022):

Thanks so much for that @LucaOverflow! I rewrote the script slightly to avoid installing the extra sshpass package and enabling root ssh login and it worked perfectly! I also actually ended up specifying the script with the post_hook directive in renewals/npm-xx.conf, just so the script wasn't called each time.

Just adding it here in case anyone else has the same issue:

#!/bin/bash


SERVER_IP=xx.xx.xx.xx
USER=username
PASSWORD=password
PRIVKEY=/path/to/privatekey

echo "Mailcow Certificate Copy started"

# find latest fullchain file
unset -v latest_fullchain
for file in /etc/letsencrypt/archive/npm-10/fullchain?.pem; do
  [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file
done
echo "Latest fullchain found: " $latest_fullchain

# find latest privkey file
unset -v latest_privkey
for file in /etc/letsencrypt/archive/npm-10/privkey?.pem; do
  [[ $file -nt $latest_privkey ]] && latest_privkey=$file
done
echo "Latest privkey found: " $latest_privkey


echo "Copying fullchain to Mailcow..."
scp -i $PRIVKEY -o "StrictHostKeyChecking no" $latest_fullchain $USER@$SERVER_IP:/home/[user]/new-certs/cert.pem

echo "Copying privkey to Mailcow..."
scp -i $PRIVKEY -o "StrictHostKeyChecking no"  $latest_privkey $USER@$SERVER_IP:/home/[user]/new-certs/key.pem

echo "Restarting mailcow containers..."
ssh -i $PRIVKEY -o "StrictHostKeyChecking no" $USER@$SERVER_IP "echo $PASSWORD | sudo -S ~/ssh_update.sh 2> /dev/null"

echo "Mailcow Certificate Copy finished"

ssh_update.sh (place somewhere in the host machine):

#!/bin/bash


if (( $EUID != 0 )); then
    echo "Please run as root"
    exit
fi

# Copy cert and key to mailcow folder
\cp /home/[user]/new-certs/cert.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem
\cp /home/[user]/new-certs/key.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem

#Restart mailcow containers
postfix_c=$(docker ps -qaf name=postfix-mailcow)
dovecot_c=$(docker ps -qaf name=dovecot-mailcow)
nginx_c=$(docker ps -qaf name=nginx-mailcow)
docker restart ${postfix_c} ${dovecot_c} ${nginx_c}
<!-- gh-comment-id:1144094357 --> @lriley2020 commented on GitHub (Jun 1, 2022): Thanks so much for that @LucaOverflow! I rewrote the script slightly to avoid installing the extra sshpass package and enabling root ssh login and it worked perfectly! I also actually ended up specifying the script with the ```post_hook``` directive in ``` renewals/npm-xx.conf```, just so the script wasn't called each time. Just adding it here in case anyone else has the same issue: ``` #!/bin/bash SERVER_IP=xx.xx.xx.xx USER=username PASSWORD=password PRIVKEY=/path/to/privatekey echo "Mailcow Certificate Copy started" # find latest fullchain file unset -v latest_fullchain for file in /etc/letsencrypt/archive/npm-10/fullchain?.pem; do [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file done echo "Latest fullchain found: " $latest_fullchain # find latest privkey file unset -v latest_privkey for file in /etc/letsencrypt/archive/npm-10/privkey?.pem; do [[ $file -nt $latest_privkey ]] && latest_privkey=$file done echo "Latest privkey found: " $latest_privkey echo "Copying fullchain to Mailcow..." scp -i $PRIVKEY -o "StrictHostKeyChecking no" $latest_fullchain $USER@$SERVER_IP:/home/[user]/new-certs/cert.pem echo "Copying privkey to Mailcow..." scp -i $PRIVKEY -o "StrictHostKeyChecking no" $latest_privkey $USER@$SERVER_IP:/home/[user]/new-certs/key.pem echo "Restarting mailcow containers..." ssh -i $PRIVKEY -o "StrictHostKeyChecking no" $USER@$SERVER_IP "echo $PASSWORD | sudo -S ~/ssh_update.sh 2> /dev/null" echo "Mailcow Certificate Copy finished" ``` ssh_update.sh (place somewhere in the host machine): ``` #!/bin/bash if (( $EUID != 0 )); then echo "Please run as root" exit fi # Copy cert and key to mailcow folder \cp /home/[user]/new-certs/cert.pem /opt/mailcow-dockerized/data/assets/ssl/cert.pem \cp /home/[user]/new-certs/key.pem /opt/mailcow-dockerized/data/assets/ssl/key.pem #Restart mailcow containers postfix_c=$(docker ps -qaf name=postfix-mailcow) dovecot_c=$(docker ps -qaf name=dovecot-mailcow) nginx_c=$(docker ps -qaf name=nginx-mailcow) docker restart ${postfix_c} ${dovecot_c} ${nginx_c} ```
Author
Owner

@LucaOverflow commented on GitHub (Aug 16, 2022):

Small Update:
The Script stopped working for me because the Certificate Number has two digits in my Installation now. The Fix is to exchange the ? to a * in the File Selector.

So in the Case of the Script I posted before:

#echo $RENEWED_DOMAINS
if [[ $RENEWED_DOMAINS == *"your.domain.tld"* ]]
then
  echo "Mailcow Certificate Copy started"

  # find latest fullchain file
  unset -v latest_fullchain
  for file in /config/letsencrypt/archive/npm-XX/fullchain*.pem; do
    [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file
  done
  echo "Latest fullchain found: " $latest_fullchain

  # find latest privkey file
  unset -v latest_privkey
  for file in /config/letsencrypt/archive/npm-XX/privkey*.pem; do
    [[ $file -nt $latest_privkey ]] && latest_privkey=$file
  done
  echo "Latest privkey found: " $latest_privkey

  echo "Updating apk repositories"
  apk update
  echo "Installing openssh and sshpass"
  apk add openssh sshpass
  echo "Copying fullchain to Mailcow"
  sshpass -p "password" scp -o "StrictHostKeyChecking no" $latest_fullchain root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/cert.pem
  echo "Copying privkey to Mailcow"
  sshpass -p "password" scp -o "StrictHostKeyChecking no"  $latest_privkey root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/key.pem
  echo "Start ssl_restart script on Mailcow VM"
  sshpass -p "password" ssh -o "StrictHostKeyChecking no" -l root MAILCOW_IP "bash /opt/mailcow-dockerized/ssl_restart.sh"
  echo "Mailcow Certificate Copy finished"
fi

Note this doesn't include the Imrovements of @lriley2020's Script. But you can just swap the Questionmarks there, too.

<!-- gh-comment-id:1216822966 --> @LucaOverflow commented on GitHub (Aug 16, 2022): Small Update: The Script stopped working for me because the Certificate Number has two digits in my Installation now. The Fix is to exchange the `?` to a `*` in the File Selector. So in the Case of the Script I posted before: ```#!/bin/bash #echo $RENEWED_DOMAINS if [[ $RENEWED_DOMAINS == *"your.domain.tld"* ]] then echo "Mailcow Certificate Copy started" # find latest fullchain file unset -v latest_fullchain for file in /config/letsencrypt/archive/npm-XX/fullchain*.pem; do [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file done echo "Latest fullchain found: " $latest_fullchain # find latest privkey file unset -v latest_privkey for file in /config/letsencrypt/archive/npm-XX/privkey*.pem; do [[ $file -nt $latest_privkey ]] && latest_privkey=$file done echo "Latest privkey found: " $latest_privkey echo "Updating apk repositories" apk update echo "Installing openssh and sshpass" apk add openssh sshpass echo "Copying fullchain to Mailcow" sshpass -p "password" scp -o "StrictHostKeyChecking no" $latest_fullchain root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/cert.pem echo "Copying privkey to Mailcow" sshpass -p "password" scp -o "StrictHostKeyChecking no" $latest_privkey root@MAILCOW_IP:/opt/mailcow-dockerized/data/assets/ssl/key.pem echo "Start ssl_restart script on Mailcow VM" sshpass -p "password" ssh -o "StrictHostKeyChecking no" -l root MAILCOW_IP "bash /opt/mailcow-dockerized/ssl_restart.sh" echo "Mailcow Certificate Copy finished" fi ``` Note this doesn't include the Imrovements of @lriley2020's Script. But you can just swap the Questionmarks there, too.
Author
Owner

@lriley2020 commented on GitHub (Aug 19, 2022):

Thanks for that @LucaOverflow! I hadn't run into that issue yet, so great to have someone else find the issue before it happened to me!

<!-- gh-comment-id:1220801968 --> @lriley2020 commented on GitHub (Aug 19, 2022): Thanks for that @LucaOverflow! I hadn't run into that issue yet, so great to have someone else find the issue before it happened to me!
Author
Owner

@vspaziani commented on GitHub (Jul 25, 2023):

Ok I am going to give this a shot since there was the question from @LucaOverflow. This is what worked for me. If you are trying to follow @lriley2020's Script, myself being a novice had to figure a couple things out... follow these instructions: (there is no problem with double digit certificate numbers since they are hard coded into the script)

  1. On your NPM host, run ssh-keygen -t rsa to generate the needed keys for SSH so that it does not require a password for SCP
  2. there should be an output that reads "Your identification has been saved in..." you need to copy that file to a location in the mallow docker container.
    2a. open your docker-compose.yml and look at the letsencrypt mapping for the container. it should look something like "./letsencrypt:/etc/letsencrypt" which means that docker is mapping ./letsencrypt (on the local machine) to "/etc/letsencrypt" (inside the container)
    2b. copy the file specified in step 2 to named /something/id_rsa to a location inside ./letsencrypt (using my previous example)
  3. on your NPM host, run ssh-copy-id _username_@_mailcow_IP_ this will copy the public key to your mail cow host
    4.create a script in "/letsencrypt/renewal-hooks/post/" using my previous example address from step 2a
  4. using the script from @lriley2020, modify the following:
    5a.look at the cert number by clicking on the three dots in the NPM web page, at the top of that popup it will give you a cert number
    5b.modify anywhere there is npm-10 in the first script from @lriley2020 with npm-(cert number from 5a)
    5c. /path/to/privatekey should be the address you copied the file to on the host machine noting that the actual mapping needs to point with reference to inside the container. For example. if I copied the file to ./letsencrypt/Renew/Fancyscript.sh on my host machine, the docker container will have it at "/etc/letsencrypt//Renew/Fancyscript.sh"
  5. edit ./letsencrypt/renewal/npm-(cert number from step 5a).conf
  6. add "post_hook = /etc/letsencrypt/renewal-hooks/post/(script name from step 4)"
  7. don't forget "ssh_update.sh" from @lriley2020's Script, that goes on the mallow machine
  8. if you run into trouble, attach to the docker container and try running the commands with the substitutions.

Special thanks to both @LucaOverflow and @lriley2020. with your point in the right direction I got it working.

<!-- gh-comment-id:1648955514 --> @vspaziani commented on GitHub (Jul 25, 2023): Ok I am going to give this a shot since there was the question from @LucaOverflow. This is what worked for me. If you are trying to follow @lriley2020's Script, myself being a novice had to figure a couple things out... follow these instructions: (there is no problem with double digit certificate numbers since they are hard coded into the script) 1. On your NPM host, run `ssh-keygen -t rsa` to generate the needed keys for SSH so that it does not require a password for SCP 2. there should be an output that reads "Your identification has been saved in..." you need to copy that file to a location in the mallow docker container. 2a. open your docker-compose.yml and look at the letsencrypt mapping for the container. it should look something like "./letsencrypt:/etc/letsencrypt" which means that docker is mapping ./letsencrypt (on the local machine) to "/etc/letsencrypt" (inside the container) 2b. copy the file specified in step 2 to named /_something_/id_rsa to a location inside ./letsencrypt (using my previous example) 3. on your NPM host, run `ssh-copy-id _username_@_mailcow_IP_` this will copy the public key to your mail cow host 4.create a script in "/letsencrypt/renewal-hooks/post/" using my previous example address from step 2a 5. using the script from @lriley2020, modify the following: 5a.look at the cert number by clicking on the three dots in the NPM web page, at the top of that popup it will give you a cert number 5b.modify anywhere there is _npm-10_ in the first script from @lriley2020 with npm-(cert number from 5a) 5c. _/path/to/privatekey_ should be the address you copied the file to on the host machine noting that the actual mapping needs to point with reference to inside the container. For example. if I copied the file to ./letsencrypt/Renew/Fancyscript.sh on my host machine, the docker container will have it at "/etc/letsencrypt//Renew/Fancyscript.sh" 6. edit ./letsencrypt/renewal/npm-(_cert number from step 5a_).conf 7. add "`post_hook = /etc/letsencrypt/renewal-hooks/post/(script name from step 4)`" 8. don't forget "ssh_update.sh" from @lriley2020's Script, that goes on the mallow machine 9. if you run into trouble, attach to the docker container and try running the commands with the substitutions. Special thanks to both @LucaOverflow and @lriley2020. with your point in the right direction I got it working.
Author
Owner

@lriley2020 commented on GitHub (Jul 25, 2023):

Seems like quite a few people are trying to find a solution to the same problem, so I thought I would share my updated workaround a year later. I'm way more happy using this in my homelab because:

  1. The nginx-proxy-manager container does not store any SSH keys (which could be very bad, especially as this container deals with all of my homelab's incoming web traffic, so lots of potential to be completely compromised)
  2. In fact, SSH is not involved at all anymore, so even less malicious potential
  3. The docker hosts's IP does not have to be hardcoded into anything
  4. Nothing is copied to the docker host

How to set up:

Edit: noticed just after posting this that none of the paths have been changed, obvs change them to suit your own setup!

  1. Edit docker-compose.yml to add the mailcow cert folder to the nginx-proxy-manager container:
version: '3'
services:
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
      - /opt/compose-stacks/mailcow-dockerized/data/assets/ssl:/mailcow-ssl    # Add this line
    healthcheck:
      test: ["CMD", "/bin/check-health"]
      interval: 10s
      timeout: 3s
    extra_hosts:
      - "host.docker.internal:host-gateway"
  1. Add this updated script to /opt/compose-stacks/nginx-proxy-manager/letsencrypt/copy_cert.sh:
#!/bin/bash

echo "Mailcow certificate copy started..."

# find latest fullchain file
unset -v latest_fullchain
for file in /etc/letsencrypt/archive/npm-10/fullchain*.pem; do
  [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file
done
echo "Latest fullchain found: " $latest_fullchain

# find latest privkey file
unset -v latest_privkey
for file in /etc/letsencrypt/archive/npm-10/privkey*.pem; do
  [[ $file -nt $latest_privkey ]] && latest_privkey=$file
done
echo "Latest privkey found: " $latest_privkey


echo "Copying fullchain to Mailcow..."
cp $latest_fullchain /mailcow-ssl/cert.pem

echo "Copying privkey to Mailcow..."
cp $latest_privkey /mailcow-ssl/key.pem

echo "Mailcow Certificate Copy finished..."

echo "Exiting, docker host should now restart affected containers..."
  1. Edit renewal config in /opt/compose-stacks/nginx-proxy-manager/letsencrypt/renewal/npm-10.conf to ensure our post renewal hook is run. Optional: also add a line to reuse the SSL key during cert renewal. If this is omitted, you will have to edit the TSLA records for your mailcow configuration every time the certificate and key are renewed together (the standard renewal behaviour):
# renew_before_expiry = 30 days
version = 2.5.0
archive_dir = /etc/letsencrypt/archive/npm-10
cert = /etc/letsencrypt/live/npm-10/cert.pem
privkey = /etc/letsencrypt/live/npm-10/privkey.pem
chain = /etc/letsencrypt/live/npm-10/chain.pem
fullchain = /etc/letsencrypt/live/npm-10/fullchain.pem

# Options used in the renewal process
[renewalparams]
account = xxxxxxxxxxxxxxxxxxxxxx
key_type = ecdsa
elliptic_curve = secp384r1
preferred_chain = ISRG Root X1
authenticator = dns-cloudflare
dns_cloudflare_credentials = /etc/letsencrypt/credentials/credentials-10
server = https://acme-v02.api.letsencrypt.org/directory
post_hook = /etc/letsencrypt/copy_cert.sh             ##### Add this line
pref_challs = dns-01, http-01
reuse_key = True                                             ###### Optionally, add this line
webroot_path = /data/letsencrypt-acme-challenge,
work_dir = /tmp/letsencrypt-lib
logs_dir = /tmp/letsencrypt-log
[[webroot_map]]
  1. Add a script to restart the affected mailcow containers, I placed mine in /opt/mailcow-cert-restart/mailcow-cert-restart.sh:
#!/bin/bash

## This script is used to restart mailcow containers when their certificates are renewed and copied into place by nginx-proxy-manager ##

if (( $EUID != 0 )); then
    echo "Please run as root"
    exit
fi

## Check if the scipt has been triggered, but the cert has not been updated ##
if [[ /opt/compose-stacks/mailcow-dockerized/data/assets/ssl/cert.pem -nt /opt/mailcow-cert-restart/.lastupdated ]]; then
    #Restart mailcow containers
    postfix_c=$(/usr/bin/docker ps -qaf name=postfix-mailcow)
    dovecot_c=$(/usr/bin/docker ps -qaf name=dovecot-mailcow)
    nginx_c=$(/usr/bin/docker ps -qaf name=nginx-mailcow)
    /usr/bin/docker restart ${postfix_c} ${dovecot_c} ${nginx_c}

    ## Update the last renewal time for the check above ##
    /usr/bin/touch /opt/mailcow-cert-restart/.lastupdated

    echo "Affected containers have been restarted"
else
    echo "The certificate has not been renewed since the last time this script ran, exiting..."
fi

... and then give it the right perms:
sudo chown root:root /opt/mailcow-cert-restart/mailcow-cert-restart.sh
sudo chmod 700 /opt/mailcow-cert-restart/mailcow-cert-restart.sh

  1. Make a systemd service that monitors the mailcow cert, and runs the script to restart the affected containers when a change is detected:
    /etc/systemd/system/mailcow-cert-restart.path:
[Path]
PathChanged=/opt/compose-stacks/mailcow/data/assets/ssl/cert.pem

[Install]
WantedBy=multi-user.target

/etc/systemd/system/mailcow-cert-restart.service:

[Unit]
Description=Restarts affected mailcow containers when their certificates are renewed by nginx-proxy-manager

[Service]
Type=oneshot
ExecStart=/opt/mailcow-cert-restart/mailcow-cert-restart.sh

[Install]
WantedBy=multi-user.target
  1. Enable the systemd service:
    sudo systemctl daemon-reload
    sudo systemctl enable --now mailcow-cert-restart

Everything should now hopefully run fine without SSH - fingers crosed ;)
Thanks again @LucaOverflow, your script is still running great for me one year later!

<!-- gh-comment-id:1650537906 --> @lriley2020 commented on GitHub (Jul 25, 2023): Seems like quite a few people are trying to find a solution to the same problem, so I thought I would share my updated workaround a year later. I'm way more happy using this in my homelab because: 1. The nginx-proxy-manager container does not store any SSH keys (which could be very bad, especially as this container deals with all of my homelab's incoming web traffic, so lots of potential to be completely compromised) 2. In fact, SSH is not involved at all anymore, so even less malicious potential 3. The docker hosts's IP does not have to be hardcoded into anything 4. Nothing is copied to the docker host How to set up: Edit: noticed just after posting this that none of the paths have been changed, obvs change them to suit your own setup! 1. Edit docker-compose.yml to add the mailcow cert folder to the nginx-proxy-manager container: ``` version: '3' services: app: image: 'jc21/nginx-proxy-manager:latest' restart: unless-stopped ports: - '80:80' - '81:81' - '443:443' volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt - /opt/compose-stacks/mailcow-dockerized/data/assets/ssl:/mailcow-ssl # Add this line healthcheck: test: ["CMD", "/bin/check-health"] interval: 10s timeout: 3s extra_hosts: - "host.docker.internal:host-gateway" ``` 2. Add this updated script to `/opt/compose-stacks/nginx-proxy-manager/letsencrypt/copy_cert.sh`: ``` #!/bin/bash echo "Mailcow certificate copy started..." # find latest fullchain file unset -v latest_fullchain for file in /etc/letsencrypt/archive/npm-10/fullchain*.pem; do [[ $file -nt $latest_fullchain ]] && latest_fullchain=$file done echo "Latest fullchain found: " $latest_fullchain # find latest privkey file unset -v latest_privkey for file in /etc/letsencrypt/archive/npm-10/privkey*.pem; do [[ $file -nt $latest_privkey ]] && latest_privkey=$file done echo "Latest privkey found: " $latest_privkey echo "Copying fullchain to Mailcow..." cp $latest_fullchain /mailcow-ssl/cert.pem echo "Copying privkey to Mailcow..." cp $latest_privkey /mailcow-ssl/key.pem echo "Mailcow Certificate Copy finished..." echo "Exiting, docker host should now restart affected containers..." ``` 3. Edit renewal config in `/opt/compose-stacks/nginx-proxy-manager/letsencrypt/renewal/npm-10.conf` to ensure our post renewal hook is run. Optional: also add a line to reuse the SSL key during cert renewal. If this is omitted, you will have to edit the TSLA records for your mailcow configuration every time the certificate and key are renewed together (the standard renewal behaviour): ``` # renew_before_expiry = 30 days version = 2.5.0 archive_dir = /etc/letsencrypt/archive/npm-10 cert = /etc/letsencrypt/live/npm-10/cert.pem privkey = /etc/letsencrypt/live/npm-10/privkey.pem chain = /etc/letsencrypt/live/npm-10/chain.pem fullchain = /etc/letsencrypt/live/npm-10/fullchain.pem # Options used in the renewal process [renewalparams] account = xxxxxxxxxxxxxxxxxxxxxx key_type = ecdsa elliptic_curve = secp384r1 preferred_chain = ISRG Root X1 authenticator = dns-cloudflare dns_cloudflare_credentials = /etc/letsencrypt/credentials/credentials-10 server = https://acme-v02.api.letsencrypt.org/directory post_hook = /etc/letsencrypt/copy_cert.sh ##### Add this line pref_challs = dns-01, http-01 reuse_key = True ###### Optionally, add this line webroot_path = /data/letsencrypt-acme-challenge, work_dir = /tmp/letsencrypt-lib logs_dir = /tmp/letsencrypt-log [[webroot_map]] ``` 4. Add a script to restart the affected mailcow containers, I placed mine in `/opt/mailcow-cert-restart/mailcow-cert-restart.sh`: ``` #!/bin/bash ## This script is used to restart mailcow containers when their certificates are renewed and copied into place by nginx-proxy-manager ## if (( $EUID != 0 )); then echo "Please run as root" exit fi ## Check if the scipt has been triggered, but the cert has not been updated ## if [[ /opt/compose-stacks/mailcow-dockerized/data/assets/ssl/cert.pem -nt /opt/mailcow-cert-restart/.lastupdated ]]; then #Restart mailcow containers postfix_c=$(/usr/bin/docker ps -qaf name=postfix-mailcow) dovecot_c=$(/usr/bin/docker ps -qaf name=dovecot-mailcow) nginx_c=$(/usr/bin/docker ps -qaf name=nginx-mailcow) /usr/bin/docker restart ${postfix_c} ${dovecot_c} ${nginx_c} ## Update the last renewal time for the check above ## /usr/bin/touch /opt/mailcow-cert-restart/.lastupdated echo "Affected containers have been restarted" else echo "The certificate has not been renewed since the last time this script ran, exiting..." fi ``` ... and then give it the right perms: `sudo chown root:root /opt/mailcow-cert-restart/mailcow-cert-restart.sh` ` sudo chmod 700 /opt/mailcow-cert-restart/mailcow-cert-restart.sh` 6. Make a systemd service that monitors the mailcow cert, and runs the script to restart the affected containers when a change is detected: `/etc/systemd/system/mailcow-cert-restart.path`: ``` [Path] PathChanged=/opt/compose-stacks/mailcow/data/assets/ssl/cert.pem [Install] WantedBy=multi-user.target ``` `/etc/systemd/system/mailcow-cert-restart.service`: ``` [Unit] Description=Restarts affected mailcow containers when their certificates are renewed by nginx-proxy-manager [Service] Type=oneshot ExecStart=/opt/mailcow-cert-restart/mailcow-cert-restart.sh [Install] WantedBy=multi-user.target ``` 7. Enable the systemd service: ` sudo systemctl daemon-reload` `sudo systemctl enable --now mailcow-cert-restart` Everything should now hopefully run fine without SSH - fingers crosed ;) Thanks again @LucaOverflow, your script is still running great for me one year later!
Author
Owner

@Jackk91 commented on GitHub (Jun 19, 2024):

thanks @lriley2020

<!-- gh-comment-id:2179121421 --> @Jackk91 commented on GitHub (Jun 19, 2024): thanks @lriley2020
Author
Owner

@protonaut commented on GitHub (Jul 23, 2024):

I followed your instruction. But I don't know how to get the account ID from letsencrypt for /opt/compose-stacks/nginx-proxy-manager/letsencrypt/renewal/npm-10.conf

account = xxxxxxxxxxxxxxxxxxxxxx

Any quick help? Thanx

<!-- gh-comment-id:2245510361 --> @protonaut commented on GitHub (Jul 23, 2024): I followed your instruction. But I don't know how to get the account ID from letsencrypt for `/opt/compose-stacks/nginx-proxy-manager/letsencrypt/renewal/npm-10.conf` account = xxxxxxxxxxxxxxxxxxxxxx Any quick help? Thanx
Author
Owner

@lriley2020 commented on GitHub (Jul 23, 2024):

I followed your instruction. But I don't know how to get the account ID from letsencrypt for /opt/compose-stacks/nginx-proxy-manager/letsencrypt/renewal/npm-10.conf

account = xxxxxxxxxxxxxxxxxxxxxx

Any quick help? Thanx

I just used the xxxxxx to censor my file so that other people can't see my LE account ID. It should have already been written automatically to the config file by NPM - all you are doing is adding a line to this file. Make sure that you double check the cert number in the NPM interface (click on 3 dots) and then check you are looking in the corresponding directory for this certificate.

<!-- gh-comment-id:2246174838 --> @lriley2020 commented on GitHub (Jul 23, 2024): > I followed your instruction. But I don't know how to get the account ID from letsencrypt for `/opt/compose-stacks/nginx-proxy-manager/letsencrypt/renewal/npm-10.conf` > > > > account = xxxxxxxxxxxxxxxxxxxxxx > > > > Any quick help? Thanx I just used the xxxxxx to censor my file so that other people can't see my LE account ID. It should have already been written automatically to the config file by NPM - all you are doing is adding a line to this file. Make sure that you double check the cert number in the NPM interface (click on 3 dots) and then check you are looking in the corresponding directory for this certificate.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/nginx-proxy-manager-NginxProxyManager#656
No description provided.