Server-side safety

Limit abuse and automated submissions

Add rate limits, honeypots, challenge widgets, and monitoring without making the form unnecessarily hostile to real people.

Put a form on a public page and bots will find it. No single control stops all of them. What works is a few cheap layers, each catching a different kind of abuse.

Know what abuse costs you

Ask what happens when the form is hit 10,000 times. A contact form sends 10,000 emails from your domain and your sender reputation suffers. A password-reset form tells the attacker which addresses have accounts. An upload form burns storage and CPU.

Protect the expensive operation, not the HTML page. The email, the database write, and the file processing are what you’re defending.

Rate limits

Count requests per key on the server and refuse when the count is too high. An IP address is the easy default, but a mobile carrier can put thousands of people behind one address, and an attacker can rotate through many. For anything behind a login, the account ID is a stronger key. Sensitive flows use both.

When the limit trips, return 429 Too Many Requests with a short generic message. Log enough to see the pattern later, without storing the submitted content.

A honeypot

A honeypot is a field a person never sees but a naive bot fills in because it’s there:

<div hidden>
  <label for="company-website">Leave this field empty</label>
  <input id="company-website" name="companyWebsite" tabindex="-1" autocomplete="off">
</div>

hidden keeps it out of the page. tabindex="-1" keeps keyboard users from landing on it. autocomplete="off" stops the browser from filling it. If the server receives a non-empty companyWebsite, it quietly discards the submission.

It’s free and it catches a lot of simple automation. A bot that renders the page and skips hidden fields walks right past it, so don’t stop here.

A challenge

When the layers above aren’t enough, add a challenge widget such as Cloudflare Turnstile. It runs a check in the browser and gives you a token to verify on the server. Most people never see anything.

Still, a challenge is friction, and it can fail for real people on strange networks or with privacy tools. I add one only when the cost of abuse justifies it.

Other signals

Request-size limits. Rejecting identical content submitted twice in a row. A form completed in under a second. Email verification before the expensive action. A queue, so a burst of legitimate work doesn’t take the site down.

Don’t block on any single weak signal. A fast submission might be a password manager. An unusual browser might be a screen reader. Combine the signals, watch what gets rejected, and adjust.

Try this: fire twenty submissions in a row, submit the same message twice, fill the honeypot, then send one slow, ordinary message. The first three should be limited or dropped. The last one must get through.

Lesson completed