Your Hotspot Firewall Blocks DHCP Before the Lease
A client subnet rule never matches the first DHCP request, because that request comes from 0.0.0.0. Allow the local path instead.
A new phone joins your hotspot, shows no address, and never gets one. The access point is fine, the DHCP daemon is running, and the firewall rule you wrote looks consistent with the address plan. What it actually does is require the client to already hold the address it is asking for. The first DHCP packet has no address at all, so the rule never fires.
Watch a client join
Follow one association from the client's side. The radio associates, the client sends a DHCP discovery, the server offers, the client requests, the server acknowledges, and only then does the client have an address usable for anything else.
Everything before that final acknowledgement happens while the client cannot use the gateway for ordinary traffic. That is the window the subnet rule forgets about.
What the first packet actually contains
RFC 2131 specifies that a client's DHCP broadcasts before it obtains an address use a source address of zero. Client-to-server messages go to UDP port 67, and server-to-client messages go to UDP port 68. The discovery therefore arrives with 0.0.0.0 as its source, and a rule requiring a source inside your client subnet simply does not match it.
That is not a misconfiguration on the client's part. It is the protocol working as designed, in the only window where it can.
Which path actually handles it
If the access point itself serves leases, this is a local service conversation, and in an IP-filtered local path it concerns input and output policy. Forwarded client traffic through the VPN is a different decision, and NAT for that traffic is different again. Conflating the three is how a narrow exception becomes a broad one.
Where the packet is handled depends on the daemon and the firewall family in use. Raw-socket implementations, bridge arrangements and earlier filtering hooks can move or block it somewhere else entirely. Confirm the daemon's interface binding and the hooks in play before adding rules.
A generic input/output recipe copied from a forum is not evidence that your implementation traverses those chains the same way.
The reply is not always a broadcast
An exception that permits only broadcast replies is also wrong. The DHCP reply destination depends on the client's state and the message fields, and the protocol specifies both broadcast and unicast cases. If your server replies unicast to an address it is about to assign, a broadcast-only rule blocks the step that finishes the job. Write the rule against the exchange the configured server actually performs.
Later lease renewals repeat the exchange in a different state, so leave room for them rather than only for the first join.
Fix narrowly, then check the rest
Keep the existing policy, find the missing DHCP allowance, and scope it to the client-facing interface, the protocol and the two ports. Prove the request reaches the service, then prove the service answers. Those are two different observations, and service or pool configuration can still be wrong behind perfect filtering.
Flushing a table is not a DHCP configuration step. In iptables, flush deletes rules from the selected chain or table, and flushing the NAT table does not create a translation rule. Setting broad ACCEPT policies removes protection unrelated to leasing, which is why neither belongs in a reusable hotspot fix.
Once the client holds a lease, check DNS, forwarding and VPN egress as separate tasks. A lease establishes local addressing and nothing beyond it.