Let's Encrypt automatic certificate renewal

Hi,
Is there any known reason why my certificate is not being renewed automatically?
WWW is enabled, and the firewall rule looks okay to me.
2022-09-15_13-28-01.jpg
2022-09-15_13-16-40.jpg

I hate to look at screenshots, please post full text export of configuration. Only then we’ll be able to comment on the “firewall rule looks okay” part of quoted post.

Hi,
Thank you for your reply. Please, note that as I was exporting my config the “www” service was not in the export. However, In Winbox it shows it as an enabled service.
Maybe due to my mistake in disabling something at “/IP/firewall/service-port”, IDK.

ip service print
Flags: X, I - INVALID
Columns: NAME, PORT, CERTIFICATE, VRF

NAME PORT CERTIFICATE VRF

0 X telnet 23 main
1 X FTP 21
2 www 80 main
3 X ssh 22 main
4 www-ssl 443 letsencrypt-autogen_2022-09-14T15:11:35Z main
5 X API 8728 main
6 winbox redacted main
7 X api-ssl 8729 none main

Here is my config, Please, be gentle. :smiley:
hap3.txt (23.3 KB)

I am experiencing the same issue as OP, In Firewall filter dstTCP80 is accepted on input. www service on port 80 is enabled. After issueing

/certificate enable-ssl-certificate dns-name=domain.example.com

i get the following result:

progress: [success] ssl certificate updated

cert is renamed but not renewed. Now it is expiring in 3 days. :frowning:
my config bellow:

/ip service
set telnet address="" disabled=yes port=23 vrf=main
set ftp address="" disabled=no port=21
set www address="" disabled=no port=80 vrf=main
set ssh address="" disabled=no port=22 vrf=main
set www-ssl address="" certificate=le-cert.cer disabled=yes port=443 tls-version=any vrf=main
set api address="" disabled=yes port=8728 vrf=main
set winbox address="" disabled=no port=8291 vrf=main
set api-ssl address="" certificate=none disabled=yes port=8729 tls-version=any vrf=main
/ip firewall filter 
add action=accept chain=input comment=ALLOW_HTTP_FOR_LETSENCRYPT_VALIDATION disabled=no place-before=0 protocol=tcp port=80

edit:
what is even weirder “expires-after” fields does not match in cli and winbox. CLI shows 1w4d while winbox shows 3d

This is what I came up with.
Interval 80d 00:00:00

:log info "Script - Certificate renewal"
:local ipWWW [/ip/service find name=www];
/ip/service set $ipWWW disabled=no
/ip firewall filter set disabled=no [find comment="IP Services HTTP"]
certificate remove [find common-name="CERT-CN"];
certificate enable-ssl-certificate dns="CERT-DNS-RECORD";
:delay 30s
:log info "Script - Certificate renewal | UM certificate update"
:local certCN [/certificate find common-name=CERT-CN];
:local certN [/certificate get $certCN value=name];
/user-manager set certificate=$certN
:delay 5s
:log info "Script - Certificate renewal | UM CERT- done!"
:log info "Script - Certificate renewal | WIFI EAP certificate update"
:local wifiEAP [/interface/wireless/security-profiles find name=EAP];
/interface/wireless/security-profiles/ set $wifiEAP tls-certificate=$certN
:delay 5s
:log info "Script - Certificate renewal | WIFI EAP CERT- done!"
:log info "Script - Certificate renewal | IP Services certificate update"
:local certWWW [/ip/service find name=www-ssl];
/ip/service set $certWWW certificate=$certN
:delay 5s
:log info "Script - Certificate renewal | IP Services CERT- done!"
:delay 5s
:local ipWWW [/ip/service find name=www];
/ip/service set $ipWWW disabled=yes
/ip firewall filter set disabled=yes [find comment="IP Services HTTP"]
:delay 5s
:log info "Script - Certificate renewal | www service & firewall rule are disabled."

I do NOT fix your script, or test it, I’m rewriting it correctly as example for you to do script on correct way:

:log info "Script - Certificate renewal start"

:local commName "CERT-CN"
:local dnsName  "CERT-DNS-RECORD"

/ip service
set www disabled=no

/ip firewall filter
enable [find where comment="IP Services HTTP"]

/certificate
remove [find where common-name=$commName]
enable-ssl-certificate dns=$dnsName

# better insert here a loop that check when cert is ready, or timeout after x seconds
:delay 30s

/certificate
:local certName [get [find where common-name=$commName] name]

:log info "Script - Certificate renewal | UM certificate update"
/user-manager
set certificate=$certName
:log info "Script - Certificate renewal | UM certificate update done!"

:log info "Script - Certificate renewal | WIFI EAP certificate update"
/interface wireless security-profiles
set [find where name="EAP"] tls-certificate=$certName
:log info "Script - Certificate renewal | WIFI EAP certificate update done!"

:log info "Script - Certificate renewal | IP Services certificate update"
/ip service
set www-ssl certificate=$certName
:log info "Script - Certificate renewal | IP Services certificate update done!"

/ip service
set www disabled=yes

/ip firewall filter
disable [find where comment="IP Services HTTP"]

:log info "Script - Certificate renewal end"

version without all logging frills:

:log info "Script - Certificate renewal start"

:local commName "CERT-CN"
:local dnsName  "CERT-DNS-RECORD"

/ip service set www disabled=no
/ip firewall filter enable [find where comment="IP Services HTTP"]

/certificate
remove [find where common-name=$commName]
enable-ssl-certificate dns=$dnsName
:delay 30s
:local certName [get [find where common-name=$commName] name]

/user-manager set certificate=$certName

/interface wireless security-profiles set [find where name="EAP"] tls-certificate=$certName

/ip service set www-ssl certificate=$certName

/ip service set www disabled=yes
/ip firewall filter disable [find where comment="IP Services HTTP"]

:log info "Script - Certificate renewal end"

Both version suppose that the rule on firewall filter commented with “IP Services HTTP” exist.
When you share a script, share also instructions, and check if it work only on your device because are present other items not writed on post.

As I said in the other post, it certainly could use your touch/magic. I appreciate your time and effort. I will do better next time now that I have an example to follow. Although, at this point, what you have suggested "better insert a loop that checks when cert is ready or timeout after x " is beyond my knowledge.

My goal in these cases is to teach, not to solve… (usually I not solve if on that moment I can not test what I write)

Sorry if I don’t explain well or I seem rude, but not being English I have difficulty expressing myself,
often using automatic translators the sentences are worse than if I write them directly, so I often explain little, I let the scripts speak.

(●’◡’●)

It’s okay, I understand as I suffer the same way.
Personally, I check everything I write in English on Grammarly and after that in google translate it to my first language, and still, it confuses people.

thanks “rextended” very clear script. I test this script and added SSTP function. worked fine to me. :smiley:

log info "Script - Certificate renewal start"

:local commName "you're Domain Common Name"
:local dnsName  "you're Domain Name"

/ip service
set www disabled=no

/ip firewall filter
enable [find where comment="IP Services HTTP"]

/certificate
remove [find where common-name=$commName]
enable-ssl-certificate dns=$dnsName

# better insert here a loop that check when cert is ready, or timeout after x seconds
:delay 30s

/certificate
:local certName [get [find where common-name=$commName] name]

:log info "Script - Certificate renewal | UM certificate update"
/user-manager
set certificate=$certName
:log info "Script - Certificate renewal | UM certificate update done!"

:log info "Script - Certificate renewal | WIFI EAP certificate update"
/interface wireless security-profiles
set [find where name="EAP"] tls-certificate=$certName
:log info "Script - Certificate renewal | WIFI EAP certificate update done!"

:log info "Script - Certificate renewal | IP Services certificate update"
/ip service
set www-ssl certificate=$certName
:log info "Script - Certificate renewal | IP Services certificate update done!"

:log info "Script - Certificate renewal | SSTP Server  certificate update"
/interface sstp-server server
set certificate=$certName
:log info "Script - Certificate renewal | SSTP Server  certificate update done!"

/ip service
set www disabled=yes

/ip firewall filter
disable [find where comment="IP Services HTTP"]

:log info "Script - Certificate renewal end"

If it would be helpful to you I wrote an auto-renewal script, you can find it @ https://github.com/pincioc/LetsEncrypt_OSScript

Ratings and comments are highly appreciated.

Mauro

Good afternoon Mauro!
Please help me.

I am running your script:
https://github.com/pincioc/LetsEncrypt_OSScript/issues/new

Line of code

  :if ( [/certificate enable-ssl-certificate dns-name=$dnsName] = 0 ) do={ ...
} else={
         :log info "[Letsencrypt_OSScript] - Nothing to do!"
}

always returns non-zero.
Every time I run the script, I get a log message “[Letsencrypt_OSScript] - Nothing to do!”
But this updates the certificate anyway!

I can’t find documentation of what type of data the command should return

[/certificate enable-ssl-certificate dns-name=$dnsName]

Please share information about the return data type for the function

[/certificate enable-ssl-certificate dns-name=$dnsName]

.

Many thanks!

The script is full of errors,

Read instead the manuals & see the videos.
https://help.mikrotik.com/docs/display/ROS/Certificates#Certificates-Let’sEncryptcertificates

Good day.
Line of code

  :if ( [/certificate enable-ssl-certificate dns-name=$dnsName] = 0 ) do={ ...
} else={
         :log info "[Letsencrypt_OSScript] - Nothing to do!"
}

always returns non-zero.

I rewrote this part of the script.
Perhaps someone will find it useful.

#script name
:local scrname "scr-LetsEncrypt-vpn-sampledomain"

:local Daysbefore 30
:local dnsName "vpn.sampledomain.com"

#cert name
:local certName "crt-vpn-le"

:local array [ :toarray "" ]
:local newvalue ""


:if ( ([/certificate/print count-only where common-name =$dnsName] = 0 ) || ( ([/certificate/print count-only where common-name=$dnsName] > 0) && ([/certificate/get value-name=days-valid [find common-name=$dnsName]] <  $Daysbefore) ) ) do={

	:log info "$scrname - Try to open port in firewall and enable service"
	/ip firewall/filter/add chain=input dst-port=80 protocol=tcp action=accept place-before=0 comment=$scrname
	/ip service enable [find port=80]

	:log info "$scrname - Start certificate update process"
	:set $newvalue [/certificate enable-ssl-certificate dns-name=$dnsName as-value]
	:log info "$scrname - Stop certificate update process"
	:delay 20000ms
	:log info "$scrname - Certificate update message: $[:tostr $newvalue]"

	:if ([:typeof [:find [:tostr $newvalue] "[error]"]]="nil") do={
		:log info "$scrname - Certificate Updated - Close doors"
		
		#Optional code. Can be deleted. START
		#Here I am renaming the resulting certificate, since I am using dual WANs and two different certificates for my ipsec RoadWarriors.

		:set $newvalue [/certificate/get value-name=name [find name~$certName]]
		/certificate remove [find name~$certName]
		:log info "$scrname - Certificate $newvalue removed"
		
		:local dttm [:tostr [/system clock get date]]
		:set newvalue ( $newvalue . [:pick $dttm 0 3] . [:pick $dttm 4 6] . [:pick $dttm 7 11] . "-")
		:set $dttm [:tostr [/system clock get time]]
		:set newvalue ( $newvalue . [:pick $dttm 0 2] . [:pick $dttm 3 5] . [:pick $dttm 6 8])
		:set newvalue ( $certName . "-" . $newvalue )

		/certificate set name=$newvalue [find name~"letsencrypt-autogen"]
		:log info "$scrname - Certificate renamed to $newvalue"

	
		#####
		:set newvalue [/certificate get [/certificate find where common-name=$dnsName] name ]
		:set $array ( $array, $newvalue );
		# :put [:tostr $array] 

		#R3 - its LetsEncrypt R3 for my RoadWarriors
		:set $newvalue [/certificate get [/certificate find where common-name="R3"] name ]
		# :put $newvalue
		:set $array ( $array, $newvalue );
		# :put [:tostr $array]
		/ip/ipsec/identity set [/ip/ipsec/identity find where  comment="RoadWarriors"] certificate=$array
		:log info "$scrname - array: $[:tostr $array]"
		#####

		#Optional code. Can be deleted. END
		
	} else={
		:log error "$scrname - Unable to generate certification - Close doors"
	}
     
	
	log info "$scrname - Remove custom firewall roule and disable services"
	/ip firewall/filter/remove [find comment=$scrname]
	/ip service disable [find port=80]

} else={
	:log info "$scrname - Nothing to do!"
}

Sorry, but is STILL full of errors…

Good day!
I am not a programmer.
This script works for me on CCR2004 ROS 7.8
If it’s not difficult for you, please point out the mistakes.
Many thanks!

I used your code on X86 platform. It works to create the certificate but fail to renew the certificate even it is < 30 days valid. I found that the code following:

:if ( ([/certificate/print count-only where common-name =$dnsName] = 0 ) || ( ([/certificate/print count-only where common-name=$dnsName] > 0) && ([/certificate/get value-name=days-valid [find common-name=$dnsName]] < $Daysbefore) ) ) do={

may be wrong. I checked that “[/certificate/get value-name=days-valid [find common-name=$dnsName]” is the root of problem. It cannot draw the value of days-valid. Therefore, script will never enter renewal process.

I rewrite it into this version

:if ( ([/certificate/print count-only where common-name =$dnsName] = 0 ) || ( ([/certificate/print count-only where common-name=$dnsName] > 0) && ([/certificate get [find common-name=$dnsName] days-valid] < $Daysbefore) ) ) do={

It can read the days-valid correctly by verify to display value in log

:log info [/certificate get [find common-name=$dnsName] days-valid]

Please update me if I am misunderstanding

Using the days-valid value is not applicable, this value is static, it is the validity of the certificate, not how many days it will expire.
Here is my script based by expiration, run daily, if the environment DAYS is greather than certificate “Expires After” , the certificate will be renewed.

######## LETSENCRYPT AUTOUPDATE SCRIPT by L Hajek 08/2024 ###########

##############################################
######## ENV VARIABLES EXPLAIN ########
#
# DAYS - Type out days for renewall before certificate expire
# MAILTO - Email recipient about status of renewall
# ERRSUBJECT - Renewall failure email subject
# OKSUBJECT - Renewall success email subject
# DNSNAME - Certificate DNS names
# COMMONNAME - Certificate Common Name
# NEWVALUE - Certificate name on RouterOS (required for find certificate !)
# LOGNAME - Log purposes prefix
# SVCALLOW - WWW service allowed from
#
# For success renewall must be port 80 open from anywhere
# internet (ex. I use rule with name LETSENCRYPT for temporary
# usage in renewall process. Also I use restricted service www
# and this will be also temporary open from anywhere when is 
# renewall script running)
##############################################

### VARIABLES
:local days 20d;
:local mailto "example@domain.com";
:local errsubject "LE Certificate Renewall FAILED";
:local oksubject "LE Certificate Renewall SUCCESS";
:local dnsName "router.domain.com,host.domain.com,host2.domain2.com"
:local commonName "router.domain.com"
:local newvalue "letsencrypt-cert"
:local logname "LETSENCRYPT"
:local svcallow "10.0.0.0/8,127.0.0.1"


### CHECK
/certificate
:foreach item in=[find where common-name=rtr.lha-net.cz] do={
    :if (([get $item expires-after] < $days) and ![get $item expired]) do={
		:log info "$logname - Opening Ports Firewall"
		/ip firewall/filter/enable [find comment=LETSENCRYPT]
		/ip service/set www address=""
		:delay 5s
		:log info "$logname - Starting Certificate Renewall"
		:set $newvalue [/certificate enable-ssl-certificate dns-name=$dnsName as-value]
		:delay 60s
		:log info "$logname - Stop Certificate Update Process"
		:log info "$logname - Certificate Update Status: $[:tostr $newvalue]"
		/ip service/set www address="$svcallow"
		/ip firewall/filter/disable [find comment=LETSENCRYPT]
		:delay 10s
#		/certificate/remove [find where name~"letsencry*"]
		/certificate/remove [find where name="$newvalue"]
		:delay 10s
		/certificate/set [find where name~"autogen*"] name=$newvalue
		:delay 10s
		/interface/sstp-server/server/set certificate=letsencrypt-cert
		/ip/hotspot/profile/set hsprof1 ssl-certificate=letsencrypt-cert
		/ip service/set api-ssl certificate=letsencrypt-cert
		/ip service/set www-ssl certificate=letsencrypt-cert

	:if ([:typeof [:find [:tostr $newvalue] "[error]"]]="nil") do={
		:log info "$logname - Certificate Renewall SUCCESS"
		/tool e-mail send to="$mailto" subject=([/system identity get name]. ".   $oksubject. ");
		
	} else={
		:log error "$logname - Certificate Renewall FAIL"
		/tool e-mail send to="$mailto" subject=([/system identity get name]. ".   $errsubject. ");
			}
	} else={
		:log info "$logname - Certificate Valid - NOTHING TO DO"
		}
}