An IPv4 address is four numbers from 0 to 255 separated by dots. A regex can’t compare numbers, so it spells out which digit strings make 0 to 255, then repeats that piece four times with dots between.
How the octet works
25[0-5] 250 to 255
2[0-4]\d 200 to 249
1\d\d 100 to 199
[1-9]?\d 0 to 99, with no leading zero
(?:…\.){3} takes three octets each followed by a dot, and the last octet stands alone. The groups are non-capturing because nothing needs to be pulled out; if you want the four numbers, split the string on dots after the test passes.
What it accepts
The tester below checks one address per line with g and m; for a single value, drop both flags.
192.168.0.1 matches
10.0.0.255 matches
255.255.255.255 matches
0.0.0.0 matches
256.1.1.1 no: 256 is out of range
192.168.01.1 no: leading zero
1.2.3 no: three parts
1.2.3.4.5 no: five parts
Why not \d{1,3}
The short version, ^(?:\d{1,3}\.){3}\d{1,3}$, also accepts 999.999.999.999. It’s fine for picking addresses out of a log you trust, and wrong for checking input.
Leading zeros are rejected on purpose. Some parsers, including the C library’s inet_aton, read 010 as octal, which is 8, so 192.168.010.1 can mean a different machine from the one the person typed.
What it doesn’t know
It checks the format, not the meaning: it can’t tell a private range such as 10.0.0.0/8 from a public address, and it knows nothing of IPv6. In Node, net.isIPv4(text) does the same check without a regex, and net.isIP(text) returns 4, 6 or 0.
For an address with a prefix length, such as 10.0.0.0/8, add an optional suffix before the final $:
(?:\/(?:3[0-2]|[12]?\d))?
That takes /0 to /32. The CIDR & Subnet Calculator shows the range a prefix covers.