Great answers so far. I'd like to mention :
- use of UUID4's instead of 'record ids' to have unpredictable ID's in URI's
- avoid form tampering (web2py builtin, but you can do your own) if it's a human facing service
- have the admin interface in a separate application
- in a way I think using microservices helps in this sense as it's far less easy to break in a network of systems than one monolith application
- VPN's sometimes are worth the effort