Inspired by this thread I just did this, except not with the wildcard.
I have a droplet at Digital Ocean, so I used their DNS service. I configured some CNAME records for the local services, pointing to the internal names[1]. I then configured my PiHole with a local DNS CNAME record, pointing service.local.example.com to service.localdomain.
Since my PiHole is not the DHCP server, I had to add a local DNS record for service.localdomain which matched the static IP that my router gives out. It seems the conditional forwarding done by PiHole happens earlier in the resolution process.
I could then configure my services to use the DNS challenge for service.local.example.com, using the DO plugin for certbot[2] or just acme.sh[3], depending on what was available.
I didn't get it to work immediately on Android devices, until I discovered that Android only uses IPv6 DNS servers if it has an IPv6 address, and I hadn't configured that in my router. So added the static ULA address of the PiHole lease to the DHCPv6 DNS server announcement[4].
Was a bit of fiddling since I'm a networking nub, but went smoother than I had feared.
Not sure how to best distribute certificates though, if I had found a way I could let the router do all the renewals.
[1]: Not sure if this is needed but didn't bother experimenting with removing it yet.
[2]: https://certbot-dns-digitalocean.readthedocs.io/en/stable/
[3]: https://github.com/acmesh-official/acme.sh/wiki/dnsapi#20-us...
[4]: https://openwrt.org/docs/techref/odhcpd#dhcp_section