The proof is not as obvious as it might seem.
Actually, with a little change that would make the fast pointer even faster, e.g. using fast = fast.next.next.next, the algorithm is no longer guaranteed to work.
What matters is the relative speed of the two pointers.
In the standard case, the relative speed is 2 - 1 = 1, which means that at each step, the fast pointer gets one unit closer to the slow pointer. In this way, it is guaranteed that the fast one will catch up and not jump over the other.
Otherwise, e.g. if the relative speed is 3 - 1 = 2, then it is possible for them to never intersect. This would occur if we start with an odd distance between the pointers, and the cycle length is even. In this case, the distance always will always remain odd (thus it will never be zero).
To make it clear that the pointers may not intersect if not being careful about the speed difference, consider a fast pointer with speed 3, and a slow pointer with speed 1, running in a cycle with 4 nodes, labeled 0, 1, 2, 3, forming a cycle like this 0 -> 1 -> 2 -> 3 -> 0.
Assume that initially, the slow pointer is at node 0 and the fast pointer is at node 1. (note that this is not a strong assumption, and may not be alleviated by a different initialization strategy -- regardless of the initialization method, it might be the case that there are some additional nodes in the graph, not part of the cycle, making it possible for the pointers to be in arbitrary positions once they both reach the cycle).
After k steps, the slow pointer will be at node k mod 4. The fast node will be at node (1 + 3k) mod 4. Assuming there is a k such that the fast and slow pointer are at the same position, it means (1 + 3k) mod 4 = k mod 4, i.e. (1 + 2k) mod 4 = 0. But the left hand side is an odd number, thus it can not be zero. Therefore, the assumption that the pointers can point at the same node lead to a contradiction.