Anyone using ACME with Sectigo, RapidSSL, or any other inexpensive Certificate service?

When you add an address list entry with a domain name as address, the router populates dynamic entries for the same list name with IP addresses obtained from the DNS lookup. The entries have time out taken from the TTL of the records. When the timeout elapses, it will make DNS lookups again and repopulate the dynamic address list entries. You can see this recent bug fix item in 7.21 as proof that the TTL is used:

That look up uses and also updates the DNS cache under IP -> DNS, which is the same cache used when the RouterOS ACME client needs too look up the domain in the directory URL. So the ACME client will see the same IP addresses as what currently listed in the address list if the TTL is not too long because it will most probably reuse the same cached DNS record from the recent address list update.

There is a chance of mismatch if the TTL is long enough and the DNS cache is full causing old records to be evicted.

Well, that’s clever :slight_smile:

Okay, so, got the ACME certificate issued, now attempting to test getting it REissued.

I don’t see any option to force an early renewal of an ACME certificate, nor to revoke one.

So I went to the issuing CA and used its revocation process, then flushed and downloaded CRLs (though I notice that the ACME-issued certificate from Sectigo does not have the “L” flag to indicate that it participates in CRLs .. really?).

Anyway, my MikroTik still thinks the cert is valid, and no ACME traffic has been generated in the hour+a little since then (the CRL re-download time, just in case it happens to require that timeout before noticing a revocation)..

So, 1. How to force an ACME-issued certificate to renew early? And, 2. How to get RouterOS to recognize that an ACME-issued certificate that it holds has been revoked by the issuing (ACME) CA?

thanks,

In Anyone using ACME with Sectigo, RapidSSL, or any other inexpensive Certificate service? - #21 by CGGXANNX @CGGXANNX mentioned:

When you add an address list entry with a domain name as address, the router populates dynamic entries for the same list name with IP addresses obtained from the DNS lookup. The entries have time out taken from the TTL of the records. When the timeout elapses, it will make DNS lookups again and repopulate the dynamic address list entries. You can see this recent bug fix item in 7.21 as proof that the TTL is used:

I read the v7.21 release notes; I don’t know how to read into the statement the idea expressed above.

But, anyway, (on v7.22) I did:

/ip/firewall/address-list add address=acme.sectigo.com list=SectigoACME

/ip/firewall/address-list print detail

1 D ;;; acme.sectigo.com
list=SectigoACME address=91.199.212.80 creation-time=2026-03-21 17:00:49 dynamic=yes

If one did not know that the address-list had been created with a DNS name, did not know with what DNS name the list was set up, instead of the current IP address that appears in the print output above, how would one be able to know what hostname is being refreshed every TTL?

I looked at the RouterOS documentation and I don’t find an explanation of this. I do find some Forum threads where it’s alluded to. Where is it documented? And how would you see what DNS name was used that resulted in what appears in a /ip/firewall/address-list print output only as an IP address?

For dynamic address list entries which are resolved host is written in comment:

You can filter list print with comment that contains host you are looking:

/ip/firewall/address-list print where comment~"acme.sectigo.com"

and you should get entry with resolved host address.

ROS set DNS server(s) is used for resolving it - /ip/dns/print

@optio Thank you. Ohmygod I am so embarassed. Or, maybe, I need to check my reading glasses prescription.

The Comment was right there, and I just plain didn’t see it.

Right, step away from the keyboard, put on some nice, soothing music, close the eyes…. :sweat_smile:

Hm. Seems only one post can be marked as “THE Solution”, whereas various posts together in this thread - many thanks to all participants - have provided various parts of the several solutions to a variety of issues that the whole idea of ACME certificates and firewall security rules bring up.

Throughout this thread we have:

RouterOS v7.22 /certificate/add-acme WORKS.

Firewall mangle rules WORK to temporarily open inbound HTTP port 80 for ACME server Domain Validation.

/ip/firewall/address-list lists with hostnames added will automatically update at the host entry’s TTL-at-time-entered (I suppose that TTL gets updated each time at TTL RouterOS re-checks?) and the original hostname gets automatically added as a Comment so it can still be seen/ found.

/ip/service/webserver/ allows blocking all-but-ACME on port 80 (HTTP) while still allowing the necessary services on port 443 (HTTPS).

Still-open curiosity, which perhaps should now be the subject of a new thread? .. how to renew an ACME certificate early? (It seems to work to just /certificate/remove {# of the ACME cert that you want to “renew”} .. followed by re-adding it, though I wonder what the ACME server really thinks of that?).

And I need to get my eyes (and brain) checked, and spend less time in front of the pretty glowing monitor… :roll_eyes:

Thanks @optio , @CGGXANNX , @gfunkdave !

ROS is missing renew certificate command for ACME client like acme-renew (there is only for SCEP) for manual renew, so you will need to remove and add new. Try to find out by testing how on certificate issuer side is issuing handled when cert. already existing for same host. Possibility is that when isssuing new when existing is valid, that both will be valid until older expires or old will be reworked if issuer doesn’t allow you to have multiple certificates for same host (which is then basically same as renew). Also it is possible that it will throw error and you need reworke certificate before issuing new, then you are stuck with this manual issuing with ROS commands, option is to have then acme client running in container, but then you will need to create more complex ROS script to use it.

That the dynamic address list entries are tied to the TTL and automagically update once the timeout elapse is not clearly documented in the manual but is something you can observe when adding sub domains with short TTL and changing IPs, usually those pointing to hosts from CDNs.

And the change item above shows that the TTL was really used, and not some fixed interval of 5 minutes that coincidentally matches the timeout of the tested domains :slight_smile:

As for manual renewal of certificates created with add-acme, I also complained about the missing option to manually trigger it in the 7.22 release/rc thread, as add-acme only creates new entries and errors out if you specify the same name as existing entry instead of renewing it. Currently my script that attempts the renewal (and also handles toggling the firewall port and www) just creates new ACME certs with the name sub.domain.name@unix-timestamp, so that the name is guaranteed to be different from the name of the existing issued certificate.

Once that succeeds (script needs to periodically poll the value of acme-status because add-acme runs asynchronously in the background), the new certificate is set for the router services that use it (www-ssl, SSTP, User Manager, as well as being exported for unbound DoT in container). Only then is the old certificate removed (by searching for certificates with the acme-managed flag, same domain-names as the new certificate but with different fingerprint). It's safer that way because it prevents the old certificate from being removed in case there are problems with issuing the new one. Also it prevents the certificate being from removed while still being referenced by or the settings (such as www-ssl or SSTP, etc...).

This is normally not a problem. For example, when testing my scripts I could reissue certificates for the same domain within a few minutes with Let's Encrypt. Let's Encrypt documents all that here:

https://letsencrypt.org/docs/rate-limits/

For the exact same subdomain, with Let's Encrypt, you can issue the certificate 5 times within 7 days. When that limit is up, just wait for 34 hours and you can reissue a new one for the exact same domain.

So I assume the other vendors probably have similar limits that are not too restricted.

That’s why I wrote to test it just to dismiss assumptions :slight_smile: Vendors/issuers can have different logics depending on subscriptions.

Thank you again, @optio and @CGGXANNX .

In effect, I have “tesed” :laughing: Sectigo’s ACME server, by removing and re-adding the ACME certificate already twice in 24 hours. No errors so far!

Searching site:sectigo.com “rate limit” all I find is a possible API error response that a rate limit has been exceeded .. but no documentation about what those rate limits might be .. :roll_eyes: (and, since I bought the Sectigo ACME cert through GoGetSSL, I wonder whether I’ll be able to provoke a reliable reply to the question .. but, I’ll ask; GoGetSSL support has been very responsive so far).

Intersting, to create each new ACME request with a different name like that. I just re-re-re-read the ROS add-acme doc, and I see that there is a name= parameter; this is where you give each new ACME-added certificate its timestamped new name, yes?

Would you mind sharing your ROS scripts which monitor for the (successful!) ACME cert replacement and roll the services onto the new cert and remove the old cert?

again, thanks, and hoping that MikroTik eventually automates more of this.


CGGXANNX

5m

That the dynamic address list entries are tied to the TTL and automagically update once the timeout elapse is not clearly documented in the manual but is something you can observe when adding sub domains with short TTL and changing IPs, usually those pointing to hosts from CDNs.

And the change item above shows that the TTL was really used, and not some fixed interval of 5 minutes that coincidentally matches the timeout of the tested domains :slight_smile:

As for manual renewal of certificates created with add-acme, I also complained about the missing option to manually trigger it in the 7.22 release/rc thread, as add-acme only creates new entries and errors out if you specify the same name as existing entry instead of renewing it. Currently my script that attempts the renewal (and also handles toggling the firewall port and www) just creates new ACME certs with the name sub.domain.name@unix-timestamp, so that the name is guaranteed to be different from the name of the existing issued certificate.

Once that succeeds (script needs to periodically poll the value of acme-status because add-acme runs asynchronously in the background), the new certificate is set for the router services that use it (www-ssl, SSTP, User Manager, as well as being exported for unbound DoT in container). Only then is the old certificate removed (by searching for certificates with the acme-managed flag, same domain-names as the new certificate but with different fingerprint). It's safer that way because it prevents the old certificate from being removed in case there are problems with issuing the new one. Also it prevents a certificate being removed while still being referenced by or the settings (such as www-ssl or SSTP, etc...).

This is normally not a problem. For example, when testing my scripts I could reissue certificates for the same domain within a few minutes with Let's Encrypt. Let's Encrypt documents all that here:

https://letsencrypt.org/docs/rate-limits/

For the exact same subdomain, with Let's Encrypt, you can issue the certificate 5 times within 7 days. When that limit is up, just wait for 34 hours and you can reissue a new one for the exact same domain.

So I assume the other vendors probably have similar limits that are not too restricted.

This is the script that I needed to quickly adapt for add-acme (as previously my script used enable-ssl-certificate) once I've found out that the upgrade to 7.22 tried to "upgrade" the old certificate and failed every time, although the old cert is still valid for 1.5 months (I had a few posts about that in the 7.22rc thread).

While the add-acme process is ongoing, the field acme-status of the certificate quickly goes through a bunch of "steps". I tried to catch all of them to classify them as part of the "pending" state but probably missed many, so now my waiting loop breaks on some words that appears in success or error message only, as well as a hard wait limit of 100 seconds.

:local domainName "xxx.yyy.zzz";
:local fwComment "allow Let's Encrypt";
:local updateWWW true;
:local updateAPI true;
:local updateSSTP true;
:local updateUM true;
:local exportCertPath "docker/config/unbound/acme-exp";
:local exportCertPass "abcd1234";

:local leCertName ($domainName . "@" . [:tonum [:timestamp]]);
:local directoryUrl "https://acme-v02.api.letsencrypt.org/directory";
:local successStatus "next update at";

:log info "Requesting LE certificate renewal for $domainName..."

# temporarily open port 80 on the firewall and enable WWW service
/ipv6 firewall filter enable [find where comment=$fwComment];
/ip service enable [find name="www" !dynamic];

/certificate add-acme domain-names=$domainName name=$leCertName directory-url=$directoryUrl as-value;
:local leCertId [/certificate find name=$leCertName];
:local loopCount 0;

:do { 
    :delay 5; 
    :local currentStatus [/certificate get $leCertId acme-status];

    if ($currentStatus ~ $successStatus) do={ :break; };
    if ([:find $currentStatus "failed"] >= 0) do={ :break; };
    if ([:find $currentStatus "error"] >= 0) do={ :break; };
    if ([:find $currentStatus "too many"] >= 0) do={ :break; };

    :log info $currentStatus;
    :set loopCount ($loopCount + 1);
} while ($loopCount <= 20);

# close port 80 on the firewall and disable WWW service
/ip service disable [find name="www" !dynamic];
/ipv6 firewall filter disable [find where comment=$fwComment];

:if ([/certificate get $leCertId acme-status] ~ $successStatus) do={
    :log warning "LE certificate renewed for $domainName as $leCertName!";

    :local certFingerprint [/certificate get $leCertId fingerprint];

    :if ($updateWWW) do={
        :log info "Updating WWW-SSL to use \"$leCertName\"...";
        /ip service set [find name="www-ssl" !dynamic] certificate=none;
        /ip service set [find name="www-ssl" !dynamic] certificate=$leCertName;
    };

    :if ($updateAPI) do={
        :log info "Updating API-SSL to use \"$leCertName\"...";
        /ip service set [find name="api-ssl" !dynamic] certificate=none;
        /ip service set [find name="api-ssl" !dynamic] certificate=$leCertName;
    };

    :if ($updateSSTP) do={
        :log info "Updating SSTP to use \"$leCertName\"...";
        /interface sstp-server server set certificate=none;
        /interface sstp-server server set certificate=$leCertName;
    };

    :if ($updateUM) do={
        :log info "Updating User Manager to use \"$leCertName\"...";
        /user-manager set certificate=none;
        /user-manager set certificate=$leCertName;
    };

    :if ($exportCertPath != "") do={
        :log info "Exporting certificate to \"$exportCertPath\"...";
        /certificate export-certificate $leCertName type=pem export-passphrase=$exportCertPass file-name=$exportCertPath;
    };

    # find old certificates to remove
    :foreach leOldCert in=[/certificate find acme-managed name!=$leCertName fingerprint!=$certFingerprint domain-names=$domainName] do={ 
        :local leOldCertName [/certificate get $leOldCert name];
        :local eaf [/certificate get $leOldCert expires-after];
        :log warning "Removing old certificate $leOldCertName with EA $eaf...";
        /certificate remove $leOldCert;
    };
} else={
    :log warning "LE certificate for $domainName NOT renewed!";
};

My firewall rule that opens the port 80 has the comment set to the one in the 2nd variable. Also, my script only opens the IPv6 firewall port, not the IPv4 firewall.

Fabulous, thank you.

Some curiosities:

I notice that for each service for which you set the new certificate, you first unset the old certificate e.g. /ip service set …www-ssl… certificate=none.

What made you do it that way, instead of just setting the certificate to the newly issued cert, directly, without first removing any certificate from the service?

Cool, that you automate exporting the whole new cert (including private key).

I had noticed the quick succession of ACME certificate statuses: sigh I had assumed (silly me!) that this would be a published standard and that I could helpfully offer a list of possible status code … but, no. Not, and, can’t. Sorry :frowning:

I notice that the add-acme command does not include the eab-kid and eab-key-b64 options; was that just to sanitize the example script, or is there something in Let’s Encrypt that allows you to template those so they don’t have to be included on each add-acme command?

For temporarily opening HTTP port 80, I found the guidance offered earlier in this thread to work well (this is somewhat manually edited from my actual notes and configuration, so, apologies in advance for any errors):

#firewall: let ACME domain validation requests come in on HTTP port 80, but triggered only when an outgoing ACME request has recently occurred
/ip/firewall/address-list add list=SectigoACME address=acme.sectigo.com

/ip/firewall/mangle add chain=postrouting action=add-src-to-address-list src-address-type-local dst-address-list=SectigoACME address-list=ACME-client address-list-timeout=5m log=yes comment="When any outbound connection occurs to SectigoACME, temporarily add {my address} to list=ACME-client, which will then (temporarily) allow inbound port 80, for the ACME server to perform Domain Validation. This avoids the need to keep inbound port 80 open all the time."

/ip/firewall/filter
add chain=input action=accept disabled=no protocol=tcp dst-port=80
comment="When MikroTik ACME client reaches out (see mangle rules) the ACME-client list will temporarily be populated with {me}, which will allow incoming HTTP connections for the ACME server to perform Domain Validation. This avoids the need to keep port 80 open inbound all the time."
log=yes log-prefix=HTTP80 dst-address-list=ACME-client
in-interface={this router's interface which would receive HTTP port 80 connections as forwarded in from the Internet router}

(The above with also restrictions on what services are available on port 80, also as per earlier helpful notes in this thread):

/ip/service/webserver> print
index-plain: no
webfig-plain: no
graphs-plain: no
rest-plain: no
crl-plain: no
scep-plain: no
acme-plain: yes
index-secure: yes
webfig-secure: yes
graphs-secure: yes
rest-secure: yes

Like I wrote above, this code was updated from the years-old code that previously used enable-ssl-certificate. During the past few years, that enable-ssl-certificate also went through many changes. That old methods usually kept both the old and newly acquired certificates, and there was a period during a few RouterOS 7.x versions where both the old as well as the new certificates had the same name!!! So you had for example SSTP refering to my.domain.cert.com, but then the new certificate is also my.domain.cert.com, and if you change the SSTP setting by issuing /interface sstp-server server set certificate=my.domain.cert.com then it doesn't move to the new cert and still points to the old one. The only way to force the change was to set the setting to none first.

Of course, now that the certs have unique names (chosen by the script itself), that is no longer needed.

For Let's Encrypt (that doesn't cost money) those are not needed. Everyone can create free accounts and request certs, as long as they have proof of being able to host HTTP service at the location pointed at by the domains. But in your case with paid certs, the vendors would request those keys to be able to bind your request to the paying customer account (EAP = External Account Binding).

Yes, and using the address list entry to trigger add-src-to-address-list is totally fine. In my case because I also already explicitly enable/disable www, it's just two additional line to toggle the firewall rule.

re: Anyone using ACME with Sectigo, RapidSSL, or any other inexpensive Certificate service? - #31 by libove where rate-limits were discussed, GoGetSSL promptly responded that Sectigo’s (paid) ACME service does not have strict rate limits. Here are their words specifically, in case it’s of interest to anyone:

There is no actual limits, of course if you will do 100s of them per day, Sectigo may ask the actual reason. 10-20 per day for sure will be not an issue

Something just dawned on me. One of the principal ideas of ACME is that we can configure a certificate once, and the ACME client will keep the certificate renewed automatically over time.

But @CGGXANNX your script seems to require being run on a schedule, before each certificate would renew automatically. Doesn’t that defeat a lot of the purpose of ACME? What am I missing?

thanks.

Yes, it's intended to be run by the scheduler (interval set for 75 days), or manually whenever I want.

Without scheduler, then you don't really need a script, just use add-acme once, and RouterOS will schedule the renewal for you. I guess (but never verified) that when using the auto-renewal from RouterOS, because the certificate still has the same name and .id, no update needs to be made on settings that reference it (such as SSTP). However, I am not sure whether those running services are properly restarted to load the new certificate.

But I don't want www running all the time and also need to be able to kick off the shell script in the unbound container that replaces the DoT certificate. And because the script explicitly modifies the configuration of the referencing services, they are sure to have the configuration reloaded and the new certificate properly applied.

So, now I need to write a scheduled script to check for an ACME-issued certificate which is older than “by when the ACME client should have already renewed it” to cry for admin manual intervention…

To test this, it really would be convenient to be able to set a very short ACME-issued cert lifetime; as far as I can tell, that is not an option the ROS add-acme command offers; there is no way to set a shorter lifetime on an ACME-issued cert on the ROS side, correct? (I’ll ask the CA if they can set up something like that for me for testing). [Let’s Encrypt’s documentation offers deliberately short-lived - 6-day - certs, but that’s still too long for testing purposes]

Digging around, the one option would seem to be running my own copy of the Let’s Encrypt server, and I’m just not that interested… :sweat_smile:

I don't think there is a need to check for certificates nearing expiration. You can renew a certificate way before it really expires. In my case, the LE certificates are valid for 90 days, but you can actually keep renewing them after 34 hours and are not hit by the rate-limit, so the script running every 75 days (can also be shorter) is enough. Because in my case any older certificates for the same domain are thrown away (removed) by the script anyway, I don't care that RouterOS is unable to automatically renew them.

Yeah, with the extra of generating a new ROS name for each certificate (it seems to be that this is actually more like requesting a new certificate each time the script runs, rather than renewing a would-expire-eventually existing certificate), you’re right that there would be no need to check for certificates which are failing automatic renewal. But I’d prefer the simplicity (assuming that ACME works well, overall) of letting ROS do things automatically, and me only having active involvement (even if the “active involvement”is also by automated scripts that mostly work on their own) when something was going to actually fail. It feels simpler.