For SSH I prefer blocklistd[0], which sadly is only available on FreeBSD and NetBSD AFAIK, it's a much simpler approach though requires a small patch to any daemons that want support.
Another couple fun things for SSH are iptables string module [1] and just adding a really long version addendum. [2]
Silly examples but the bots get confused. I admit these are not efficient rules. Try it out on that domain. Send some SSH bots my way, I honestly have no idea what it looks like on the bot-end-of-things. I would estimate about 95% of bots are libssh. About 4% are Go and the rest are an odd mix. That string can be spoofed but I think most people are too lazy.
I forgot to mention, if the IPtables examples are useful they can make things ever more quiet if instead of "-j DROP" one uses "-j SET options" to add to an ipset, then use that same ipset in an outbound rule to tcp-reset the openssh socket to free up memory faster. Not really a big deal unless one gets flooded but probably useful to someone some day. Maybe I will do a little write-up on this.
[0] https://youtu.be/fuuf8G28mjs?si=UhuRVndacryMim_a