52.0 How to use this chapter#
- Every other chapter in this book teaches. This one does not teach. It answers.
- It is the chapter you open when you are already in the middle of something and you need one number, one flag, or one next step.
- Nothing here is new. Every fact appeared earlier in the book, in context, with the explanation attached. Here it is stripped down to the lookup.
- The chapter is arranged so you can find things without reading. The section numbers below are the map.
- Sections 52.1 to 52.3 are numbers: latency, powers of two, units, bases, character codes. Use these when you are estimating.
- Section 52.4 is the networking reference: layers, addresses, ports, status codes, record types, states. Use this when you are reading a capture or a log.
- Section 52.5 is the networking command cheat sheet. Every command has two extra columns that most cheat sheets leave out: what a pass proves, and what a pass does not prove. That distinction is the whole diagnostic method of this book.
- Section 52.6 is a decision tree for “I cannot reach a site”. It is written as a branching procedure. Follow the numbers.
- Sections 52.7 and 52.8 are the same two things for git: a full command cheat sheet, then a decision tree for a failed git operation.
- Sections 52.9 to 52.16 are the rest of the desk reference: shell, file magic numbers, complexity, cloud, security, AI, regular expressions, editors.
- Section 52.17 is what to do when none of the above worked.
- Two conventions run through the chapter. First, macOS syntax is given first, because that is what the reader of this book is using, with the Linux difference noted on the next line. Second, anything that is a convention rather than a standard is labelled as such.
- Anything marked “approximate” is an order of magnitude, not a measurement. Order of magnitude is usually what you need. A number that is right to within a factor of two will stop you making the wrong decision.
- One warning before you start. A reference table tells you what a thing is. It does not tell you whether the thing is your problem. Keep the habit of asking what an observation rules out, not just what it looks like.
52.1 Numbers every engineer should know#
52.1.1 Why this section exists#
- Most engineering decisions are decided by a factor of a thousand, not by a factor of two.
- If you know that memory is roughly one hundred times slower than cache, and a network hop across the planet is roughly a million times slower than memory, you will make the right architectural call without measuring.
- The list below descends from fastest to slowest. Learn the shape, not the digits.
- The original list of this kind is credited to Jeff Dean of Google, who presented “Numbers Everyone Should Know” in a 2009 keynote. Peter Norvig published a similar table earlier, in his 2001 essay “Teach Yourself Programming in Ten Years”.
- The figures below are updated for typical 2025 to 2026 hardware. They are approximate.
52.1.2 The latency ladder, real figures#
| 1 CPU cycle at 3 GHz |
0.33 ns |
one tick |
| L1 cache reference |
1 ns |
4 to 5 cycles |
| Branch mispredict |
3 to 5 ns |
pipeline refill |
| L2 cache reference |
4 ns |
about 14 cycles |
| L3 cache reference |
15 to 40 ns |
shared, slower |
| Mutex lock and unlock |
17 ns |
uncontended |
| Main memory reference |
80 to 100 ns |
DDR4 or DDR5 |
| Read 1 MB from RAM |
50 us |
one core, 20 GB/s |
| NVMe SSD 4 KiB read |
20 to 100 us |
random |
| Read 1 MB from NVMe |
150 to 300 us |
sequential |
| Same-rack round trip |
0.1 ms |
switch to switch |
| Same-datacentre round trip |
0.5 ms |
500 us |
| HDD seek, 7200 rpm |
8 ms |
seek plus rotate |
| Read 1 MB from HDD |
5 ms |
sequential |
| Same-city round trip |
5 to 10 ms |
metro fibre |
| Same-continent round trip |
20 to 50 ms |
|
| Mumbai to Frankfurt |
110 to 130 ms |
approximate |
| Mumbai to Virginia |
190 to 220 ms |
approximate |
| Cross-continent worst case |
250 to 300 ms |
|
| Geostationary satellite |
480 to 600 ms |
35,786 km up |
| Low-earth-orbit satellite |
25 to 60 ms |
about 550 km up |
- The single most useful line in that table is the second-from-last group: light in glass fibre travels at about 200,000 kilometres per second, which is roughly two thirds of the speed of light in vacuum.
- That gives a rule you can do in your head: about 1 millisecond of round trip per 100 kilometres of fibre.
- Check it. Mumbai to London is about 7,200 km in a straight line and about 10,000 km of real cable route. Ten thousand divided by one hundred is 100 milliseconds. Measured round trips on that path are 110 to 130 ms.
- This rule is physics. No amount of money buys past it. It is why content delivery networks exist: you cannot make the light faster, so you move the copy closer.
52.1.3 The latency ladder, scaled to human time#
- Multiply every figure by one billion. One nanosecond becomes one second.
- Now the ladder becomes something a person can feel.
| L1 cache reference |
1 ns |
1 second |
| L2 cache reference |
4 ns |
4 seconds |
| L3 cache reference |
15 ns |
15 seconds |
| Main memory reference |
100 ns |
1 minute 40 seconds |
| NVMe SSD read, fast |
20 us |
5.6 hours |
| NVMe SSD read, typical |
100 us |
1.2 days |
| Same-datacentre round trip |
0.5 ms |
5.8 days |
| Read 1 MB from HDD |
5 ms |
2 months |
| HDD seek |
8 ms |
3 months |
| Mumbai to Frankfurt |
120 ms |
3.8 years |
| Mumbai to Virginia |
200 ms |
6.3 years |
| Cross-continent worst case |
300 ms |
9.5 years |
| Machine reboot |
30 s |
950 years |
| The reader’s curl timeout |
15 s |
475 years |
- Read the last two lines again. When the reader’s
curl waited fifteen seconds and gave up, the processor had, in its own sense of time, waited several human centuries for an answer that never came.
- That is why a timeout is such strong evidence. Nothing on a working network takes that long. Silence for fifteen seconds is not slowness. It is absence.
52.1.4 Powers of two, 2^0 to 2^64#
- Every size in computing is a power of two or a multiple of one.
- Learn 2^10 = 1024, 2^20 = about one million, 2^30 = about one billion. The rest follows.
| 2^0 |
1 |
- |
| 2^1 |
2 |
- |
| 2^2 |
4 |
- |
| 2^3 |
8 |
one byte in bits |
| 2^4 |
16 |
one hex digit range |
| 2^5 |
32 |
- |
| 2^6 |
64 |
- |
| 2^7 |
128 |
ASCII range |
| 2^8 |
256 |
one byte of values |
| 2^9 |
512 |
classic disk sector |
| 2^10 |
1024 |
1 KiB |
| 2^11 |
2048 |
2 KiB |
| 2^12 |
4096 |
4 KiB, one page |
| 2^13 |
8192 |
8 KiB |
| 2^14 |
16384 |
16 KiB |
| 2^15 |
32768 |
32 KiB, int16 max+1 |
| 2^16 |
65536 |
64 KiB, port count |
| 2^17 |
131072 |
128 KiB |
| 2^18 |
262144 |
256 KiB |
| 2^19 |
524288 |
512 KiB |
| 2^20 |
1048576 |
1 MiB |
| 2^21 |
2097152 |
2 MiB |
| 2^22 |
4194304 |
4 MiB |
| 2^23 |
8388608 |
8 MiB |
| 2^24 |
16777216 |
16 MiB, colours |
| 2^25 |
33554432 |
32 MiB |
| 2^26 |
67108864 |
64 MiB |
| 2^27 |
134217728 |
128 MiB |
| 2^28 |
268435456 |
256 MiB |
| 2^29 |
536870912 |
512 MiB |
| 2^30 |
1073741824 |
1 GiB |
| 2^31 |
2147483648 |
2 GiB, int32 max+1 |
| 2^32 |
4294967296 |
4 GiB, IPv4 space |
| 2^33 |
8589934592 |
8 GiB |
| 2^34 |
17179869184 |
16 GiB |
| 2^35 |
34359738368 |
32 GiB |
| 2^36 |
68719476736 |
64 GiB |
| 2^37 |
137438953472 |
128 GiB |
| 2^38 |
274877906944 |
256 GiB |
| 2^39 |
549755813888 |
512 GiB |
| 2^40 |
1099511627776 |
1 TiB |
| 2^41 |
2199023255552 |
2 TiB |
| 2^42 |
4398046511104 |
4 TiB |
| 2^43 |
8796093022208 |
8 TiB |
| 2^44 |
17592186044416 |
16 TiB |
| 2^45 |
35184372088832 |
32 TiB |
| 2^46 |
70368744177664 |
64 TiB |
| 2^47 |
140737488355328 |
128 TiB, x86 user VA |
| 2^48 |
281474976710656 |
256 TiB, MAC space |
| 2^49 |
562949953421312 |
512 TiB |
| 2^50 |
1125899906842624 |
1 PiB |
| 2^51 |
2251799813685248 |
2 PiB |
| 2^52 |
4503599627370496 |
4 PiB, double mantissa |
| 2^53 |
9007199254740992 |
8 PiB, exact int limit |
| 2^54 |
18014398509481984 |
16 PiB |
| 2^55 |
36028797018963968 |
32 PiB |
| 2^56 |
72057594037927936 |
64 PiB |
| 2^57 |
144115188075855872 |
128 PiB |
| 2^58 |
288230376151711744 |
256 PiB |
| 2^59 |
576460752303423488 |
512 PiB |
| 2^60 |
1152921504606846976 |
1 EiB |
| 2^61 |
2305843009213693952 |
2 EiB |
| 2^62 |
4611686018427387904 |
4 EiB |
| 2^63 |
9223372036854775808 |
8 EiB, int64 max+1 |
| 2^64 |
18446744073709551616 |
16 EiB, address space |
- Three lines in that table cause more real bugs than the rest combined.
- 2^31 minus 1 is 2147483647, the largest signed 32-bit integer. This is the Year 2038 problem: a 32-bit Unix timestamp counting seconds from 1 January 1970 overflows at 03:14:07 UTC on 19 January 2038.
- 2^53 is 9007199254740992. A double-precision floating point number, the only number type JavaScript had until BigInt was standardized in ES2020, can represent every integer up to 2^53 exactly and not one above it. Git commit hashes and large database IDs sent as JSON numbers are silently corrupted here.
- 2^32 is 4294967296, the total number of IPv4 addresses. That is fewer than the number of humans alive. This is why NAT exists.
52.1.5 Decimal versus binary prefixes#
- There are two systems and they disagree by a growing amount.
- Decimal prefixes are powers of a thousand. Binary prefixes are powers of
- The binary ones were standardized by the International Electrotechnical Commission in IEC 60027-2, amendment 2, in January 1999, and now live in ISO/IEC 80000-13:2008.
| kB |
10^3 |
KiB = 2^10 |
2.4% |
| MB |
10^6 |
MiB = 2^20 |
4.9% |
| GB |
10^9 |
GiB = 2^30 |
7.4% |
| TB |
10^12 |
TiB = 2^40 |
10.0% |
| PB |
10^15 |
PiB = 2^50 |
12.6% |
| EB |
10^18 |
EiB = 2^60 |
15.3% |
| ZB |
10^21 |
ZiB = 2^70 |
18.1% |
| YB |
10^24 |
YiB = 2^80 |
20.9% |
- Who uses which, in practice, is a convention and not a standard.
- Disk and SSD makers use decimal. A “1 TB” drive holds 10^12 bytes.
- Operating systems differ. Windows reports that same drive as 931 GB, because it divides by 2^30 but writes the label “GB”. macOS since version 10.6, released in 2009, uses decimal, so it agrees with the box and says 1 TB. Linux tools vary;
df -h uses 1024 by default, df -H uses 1000.
- RAM is always binary. A “16 GB” memory module is 16 GiB, because memory is addressed by binary address lines and cannot be anything else.
- Network speeds are always decimal, and always in bits. “1 Gbit/s” is 1,000,000,000 bits per second.
- In November 2022 the 27th General Conference on Weights and Measures added four new SI prefixes: ronna for 10^27, quetta for 10^30, ronto for 10^-27 and quecto for 10^-30. There are no matching binary prefixes.
52.1.6 Common bandwidth figures#
| 10BASE-T Ethernet |
10 Mbit/s |
1990 |
| 100BASE-TX |
100 Mbit/s |
1995 |
| 1000BASE-T |
1 Gbit/s |
1999 |
| 10GBASE-T |
10 Gbit/s |
2006 |
| 400G Ethernet |
400 Gbit/s |
2017 |
| 800G Ethernet |
800 Gbit/s |
2024 |
| Wi-Fi 802.11b |
11 Mbit/s |
1999 |
| Wi-Fi 802.11g |
54 Mbit/s |
2003 |
| Wi-Fi 4, 802.11n |
up to 600 Mbit/s |
2009 |
| Wi-Fi 5, 802.11ac |
up to 6.9 Gbit/s |
2013 |
| Wi-Fi 6, 802.11ax |
up to 9.6 Gbit/s |
2021 |
| Wi-Fi 7, 802.11be |
up to 46 Gbit/s |
2024 |
| USB 2.0 |
480 Mbit/s |
2000 |
| USB 3.0 |
5 Gbit/s |
2008 |
| USB4 |
40 Gbit/s |
2019 |
| USB4 version 2 |
80 Gbit/s |
2022 |
| Thunderbolt 3 and 4 |
40 Gbit/s |
2015, 2020 |
| Thunderbolt 5 |
80 to 120 Gbit/s |
2023 |
| SATA III |
6 Gbit/s |
2009 |
| PCIe 3.0 x16 |
15.75 GB/s |
2010 |
| PCIe 4.0 x16 |
31.5 GB/s |
2017 |
| PCIe 5.0 x16 |
63 GB/s |
2019 |
| DDR4-3200, one channel |
25.6 GB/s |
2014 |
| DDR5-6400, one channel |
51.2 GB/s |
2020 |
| HBM3 per stack |
819 GB/s |
2022 |
| HBM3E per stack |
about 1.2 TB/s |
2024 |
- Note the unit change halfway down. Network and bus speeds are quoted in bits per second. Memory and PCIe totals are usually quoted in bytes per second. Mixing them up gives you an eight-fold error.
52.1.7 Typical throughput of real systems#
- These are order-of-magnitude figures for a single well-configured node in 2025 to 2026. Your numbers will differ. The shape will not.
| Redis, single core |
100k ops/s |
simple GET and SET |
| Redis, pipelined |
1M ops/s |
batched requests |
| PostgreSQL, simple reads |
10k to 50k queries/s |
indexed, cached |
| nginx, static file |
50k to 500k req/s |
small files |
| Kafka, one broker |
100 MB/s and up |
sequential writes |
| HDD, 7200 rpm |
100 to 200 IOPS |
random 4 KiB |
| SATA SSD |
up to 90k IOPS |
random 4 KiB |
| NVMe SSD |
up to 1M IOPS |
random 4 KiB |
| gzip compress |
30 to 100 MB/s |
one core |
| zstd compress, level 3 |
300 to 500 MB/s |
one core |
| SHA-256 with hardware |
1 to 2 GB/s |
one core |
| AES-256-GCM with AES-NI |
2 to 10 GB/s |
one core |
| JSON parse |
100 to 500 MB/s |
one core |
- One more figure that catches people out. A single TCP connection is limited by its window divided by the round-trip time. With a 64 KiB window and a 100 ms round trip, that is 64 KiB divided by 0.1 seconds, which is about 655 kB/s, which is about 5.2 Mbit/s.
- That is the bandwidth-delay product limit. It is why a single download from a distant server can crawl on a gigabit line, and why parallel connections or a larger window fix it. Modern systems scale the window automatically, up to 1 GiB, using the window scale option of RFC 7323.
52.2 Units and conversions#
52.2.1 Bit and byte#
- A bit is one binary digit. It is either 0 or 1.
- A byte is eight bits. This has been near-universal since the IBM System/360 in 1964. Older machines used 6, 7, 9 and 36-bit units.
- The correct formal word for exactly eight bits is octet. Networking standards use it, because “byte” once meant other sizes. RFC documents say octet.
- Lowercase b means bit. Uppercase B means byte. This is a convention, but a very strongly held one.
- A nibble is four bits, which is exactly one hexadecimal digit.
- A word has no fixed size. It means the natural unit of the processor: 16 bits on an 8086, 32 on an i386, 64 on modern x86-64 and ARM64.
52.2.2 The full prefix table#
| quetta |
Q |
10^30 |
| ronna |
R |
10^27 |
| yotta |
Y |
10^24 |
| zetta |
Z |
10^21 |
| exa |
E |
10^18 |
| peta |
P |
10^15 |
| tera |
T |
10^12 |
| giga |
G |
10^9 |
| mega |
M |
10^6 |
| kilo |
k |
10^3 |
| milli |
m |
10^-3 |
| micro |
u |
10^-6 |
| nano |
n |
10^-9 |
| pico |
p |
10^-12 |
| femto |
f |
10^-15 |
| atto |
a |
10^-18 |
| zepto |
z |
10^-21 |
| yocto |
y |
10^-24 |
| ronto |
r |
10^-27 |
| quecto |
q |
10^-30 |
- Note that kilo is lowercase k in SI. Uppercase K is not an SI prefix; it is the symbol for kelvin. Everyone writes KB anyway. That is a convention that contradicts the standard.
- The micro symbol is properly the Greek letter mu. In plain text everyone writes
u. This book writes us for microseconds.
52.2.3 MB/s versus Mb/s#
- To convert bits per second to bytes per second, divide by 8.
- To convert bytes per second to bits per second, multiply by 8.
- Some people add ten per cent for framing overhead when talking about Ethernet. That is a rough allowance, not a standard.
| 10 Mbit/s |
1.25 MB/s |
1.1 MB/s |
| 100 Mbit/s |
12.5 MB/s |
11 MB/s |
| 1 Gbit/s |
125 MB/s |
110 to 118 MB/s |
| 2.5 Gbit/s |
312.5 MB/s |
290 MB/s |
| 10 Gbit/s |
1.25 GB/s |
1.15 GB/s |
| 40 Gbit/s |
5 GB/s |
4.6 GB/s |
| 100 Gbit/s |
12.5 GB/s |
11.5 GB/s |
- Worked example. Your broadband plan says 300 Mbit/s. Divide by 8 to get 37.5 MB/s. A 4 GB file, at best, takes 4000 divided by 37.5, which is about 107 seconds. If your download manager shows 5 MB/s, you are getting about 40 Mbit/s, which is roughly one seventh of the plan.
52.2.4 Hertz to time period#
- Frequency and period are reciprocals. Period equals one divided by frequency.
| 1 Hz |
1 s |
a clock tick |
| 50 Hz |
20 ms |
mains power, India |
| 60 Hz |
16.67 ms |
mains power, USA |
| 60 Hz refresh |
16.67 ms |
standard display |
| 120 Hz refresh |
8.33 ms |
fast display |
| 1 kHz |
1 ms |
audio low end |
| 44.1 kHz |
22.7 us |
CD sample rate |
| 1 MHz |
1 us |
early CPUs |
| 1 GHz |
1 ns |
modern CPU order |
| 3 GHz |
0.333 ns |
one CPU cycle |
| 2.4 GHz |
0.417 ns |
Wi-Fi band centre |
| 5 GHz |
0.2 ns |
Wi-Fi band centre |
- Wavelength equals the speed of light divided by frequency. At 2.4 GHz that is 300,000,000 divided by 2,400,000,000, which is 12.5 centimetres. That is why a 2.4 GHz antenna is a few centimetres long.
52.2.5 dBm to milliwatts#
- dBm is power measured in decibels relative to one milliwatt. It is a logarithmic scale, so it turns multiplication into addition.
- The formula is: power in dBm equals 10 times the base-10 logarithm of the power in milliwatts.
- Two shortcuts do almost all the work. Adding 3 dB doubles the power. Adding 10 dB multiplies it by ten.
| +30 dBm |
1000 mW = 1 W |
legal max in some bands |
| +20 dBm |
100 mW |
typical router transmit |
| 0 dBm |
1 mW |
reference point |
| -30 dBm |
0.001 mW |
excellent signal |
| -50 dBm |
very small |
very good |
| -60 dBm |
very small |
good |
| -67 dBm |
very small |
minimum for video |
| -70 dBm |
very small |
usable, weak |
| -80 dBm |
very small |
unreliable |
| -90 dBm |
very small |
unusable, near noise |
- Signal strength in Wi-Fi is always a negative dBm number, because received power is always far below one milliwatt. Closer to zero is stronger. Minus 50 is much better than minus 80.
- A drop of 3 dBm means you are receiving half the power. A drop of 10 dBm means one tenth.
52.2.6 Temperature#
- To convert Celsius to Fahrenheit: multiply by 9, divide by 5, add 32.
- To convert Fahrenheit to Celsius: subtract 32, multiply by 5, divide by 9.
- To convert Celsius to Kelvin: add 273.15.
| -273.15 |
-459.67 |
absolute zero |
| 0 |
32 |
water freezes |
| 20 |
68 |
comfortable room |
| 27 |
80.6 |
warm room, India |
| 35 |
95 |
hot ambient |
| 40 |
104 |
idle CPU, normal |
| 70 |
158 |
CPU under load |
| 90 |
194 |
CPU throttling starts |
| 100 |
212 |
water boils, CPU limit |
| 105 |
221 |
typical shutdown point |
- Silicon does not fail at 90 degrees Celsius. It slows itself down on purpose, which is called thermal throttling, to avoid getting hotter. A laptop that suddenly gets slow after ten minutes of heavy work is almost always throttling.
52.2.7 Storage sizes and what fits#
| 1 byte |
one ASCII character |
| 100 bytes |
a short sentence |
| 1 KiB |
half a page of text |
| 10 KiB |
a small web page’s HTML |
| 100 KiB |
a compressed photo, low |
| 1 MiB |
a typical JPEG photo |
| 5 MiB |
a 4-minute MP3 at 160 kbit/s |
| 50 MiB |
a phone photo burst |
| 700 MiB |
one CD-ROM |
| 1 GiB |
250 songs, or 1 hour SD video |
| 4.7 GB |
one single-layer DVD |
| 8 GiB |
a small language model, 4-bit |
| 25 GB |
one Blu-ray layer |
| 50 GB |
a large modern game |
| 256 GB |
a typical phone |
| 1 TB |
250,000 photos |
| 4 TB |
a big desktop drive |
| 20 TB |
one large hard disk, 2026 |
| 1 PB |
a mid-size company’s archive |
| 1 EB |
global scale, a few of these |
- Two facts to keep the scale honest. The English Wikipedia text dump, without images and without history, is roughly 25 GB uncompressed and about 22 GB compressed as of 2025. The complete works of Shakespeare in plain text are about 5.5 MB.
52.3 Number bases and encoding#
52.3.1 Binary, decimal, hexadecimal and octal, 0 to 32#
- Hexadecimal is base 16. It uses 0 to 9 then A to F.
- One hex digit is exactly four bits. Two hex digits are exactly one byte. That is the entire reason hex exists in computing.
- Octal is base 8, three bits per digit. It survives in Unix file permissions and in C escape sequences.
| 0 |
000000 |
00 |
0 |
| 1 |
000001 |
01 |
1 |
| 2 |
000010 |
02 |
2 |
| 3 |
000011 |
03 |
3 |
| 4 |
000100 |
04 |
4 |
| 5 |
000101 |
05 |
5 |
| 6 |
000110 |
06 |
6 |
| 7 |
000111 |
07 |
7 |
| 8 |
001000 |
08 |
10 |
| 9 |
001001 |
09 |
11 |
| 10 |
001010 |
0A |
12 |
| 11 |
001011 |
0B |
13 |
| 12 |
001100 |
0C |
14 |
| 13 |
001101 |
0D |
15 |
| 14 |
001110 |
0E |
16 |
| 15 |
001111 |
0F |
17 |
| 16 |
010000 |
10 |
20 |
| 17 |
010001 |
11 |
21 |
| 18 |
010010 |
12 |
22 |
| 19 |
010011 |
13 |
23 |
| 20 |
010100 |
14 |
24 |
| 21 |
010101 |
15 |
25 |
| 22 |
010110 |
16 |
26 |
| 23 |
010111 |
17 |
27 |
| 24 |
011000 |
18 |
30 |
| 25 |
011001 |
19 |
31 |
| 26 |
011010 |
1A |
32 |
| 27 |
011011 |
1B |
33 |
| 28 |
011100 |
1C |
34 |
| 29 |
011101 |
1D |
35 |
| 30 |
011110 |
1E |
36 |
| 31 |
011111 |
1F |
37 |
| 32 |
100000 |
20 |
40 |
52.3.2 The powers of two in hexadecimal#
- In hex, every power of two is a 1, 2, 4 or 8 followed by zeros. This makes sizes instantly recognizable in a hex dump.
| 2^8 |
256 |
0x100 |
| 2^10 |
1024 |
0x400 |
| 2^12 |
4096 |
0x1000 |
| 2^16 |
65536 |
0x10000 |
| 2^20 |
1048576 |
0x100000 |
| 2^24 |
16777216 |
0x1000000 |
| 2^30 |
1073741824 |
0x40000000 |
| 2^31 |
2147483648 |
0x80000000 |
| 2^32 |
4294967296 |
0x100000000 |
| 2^40 |
1 TiB |
0x10000000000 |
| 2^48 |
256 TiB |
0x1000000000000 |
| 2^64 |
16 EiB |
0x10000000000000000 |
- When you see
0xFFFFFFFF in a log, that is 4294967295, which is the largest unsigned 32-bit value, which usually means “all bits set” or “not found”.
- When you see
0xDEADBEEF or 0xCAFEBABE, that is a human-chosen marker value, put there so it stands out in memory. 0xCAFEBABE is also the real first four bytes of a Java class file, chosen by James Gosling’s team in 1991 for exactly that reason.
52.3.3 The printable ASCII table#
- ASCII was standardized in 1963 by the American Standards Association and revised in 1967 and 1986. It defines 128 codes, 0 to 127.
- Codes 0 to 31 and 127 are control codes. Codes 32 to 126 are printable.
Dec Hx Ch Dec Hx Ch Dec Hx Ch Dec Hx Ch
32 20 SP 56 38 8 80 50 P 104 68 h
33 21 ! 57 39 9 81 51 Q 105 69 i
34 22 " 58 3A : 82 52 R 106 6A j
35 23 # 59 3B ; 83 53 S 107 6B k
36 24 $ 60 3C < 84 54 T 108 6C l
37 25 % 61 3D = 85 55 U 109 6D m
38 26 & 62 3E > 86 56 V 110 6E n
39 27 ' 63 3F ? 87 57 W 111 6F o
40 28 ( 64 40 @ 88 58 X 112 70 p
41 29 ) 65 41 A 89 59 Y 113 71 q
42 2A * 66 42 B 90 5A Z 114 72 r
43 2B + 67 43 C 91 5B [ 115 73 s
44 2C , 68 44 D 92 5C \ 116 74 t
45 2D - 69 45 E 93 5D ] 117 75 u
46 2E . 70 46 F 94 5E ^ 118 76 v
47 2F / 71 47 G 95 5F _ 119 77 w
48 30 0 72 48 H 96 60 ` 120 78 x
49 31 1 73 49 I 97 61 a 121 79 y
50 32 2 74 4A J 98 62 b 122 7A z
51 33 3 75 4B K 99 63 c 123 7B {
52 34 4 76 4C L 100 64 d 124 7C |
53 35 5 77 4D M 101 65 e 125 7D }
54 36 6 78 4E N 102 66 f 126 7E ~
55 37 7 79 4F O 103 67 g
- Four relationships in that table are worth memorizing, because they let you do character arithmetic in your head.
- The digit
0 is 48, which is hex 0x30. So the numeric value of a digit character is the character minus 48.
- Uppercase
A is 65, hex 0x41. Lowercase a is 97, hex 0x61.
- The gap between an uppercase letter and its lowercase form is exactly 32, which is a single bit: bit 5, value 0x20. Flipping that one bit changes case. That is not an accident; the 1963 committee arranged it deliberately.
- Space is 32, hex 0x20. Delete is 127, hex 0x7F.
52.3.4 Control codes and escape sequences#
\0 |
0 |
00 |
null, string end in C |
\a |
7 |
07 |
bell, terminal beep |
\b |
8 |
08 |
backspace |
\t |
9 |
09 |
horizontal tab |
\n |
10 |
0A |
line feed, newline |
\v |
11 |
0B |
vertical tab |
\f |
12 |
0C |
form feed, new page |
\r |
13 |
0D |
carriage return |
\e |
27 |
1B |
escape, starts ANSI |
\\ |
92 |
5C |
a literal backslash |
\" |
34 |
22 |
a literal double quote |
\' |
39 |
27 |
a literal single quote |
\x41 |
65 |
41 |
hex escape, gives A |
\101 |
65 |
41 |
octal escape, gives A |
\u00E9 |
233 |
E9 |
Unicode escape, e-acute |
- The line ending difference is the single most common cause of a file looking corrupted when moved between systems.
- Unix and macOS since version 10 end a line with LF alone, which is 0x0A.
- Windows ends a line with CR then LF, which is 0x0D 0x0A.
- Classic Mac OS, up to version 9 in 2001, used CR alone, 0x0D.
- In a hex dump,
0D 0A at the end of each line means the file came from Windows. In git, the setting core.autocrlf controls whether git converts between the two.
52.3.5 UTF-8 byte-length rules#
- UTF-8 was designed by Ken Thompson and Rob Pike in September 1992, famously sketched on a placemat in a diner in New Jersey. It is defined today by RFC 3629, from 2003.
- It encodes every Unicode code point in one to four bytes, and it is backward compatible with ASCII: any ASCII file is already valid UTF-8.
| U+0000 to U+007F |
1 |
0xxxxxxx |
| U+0080 to U+07FF |
2 |
110xxxxx |
| U+0800 to U+FFFF |
3 |
1110xxxx |
| U+10000 to U+10FFFF |
4 |
11110xxx |
- Every byte after the first in a multi-byte sequence has the pattern
10xxxxxx. That is the self-synchronizing property: from any byte you can find the start of the character by scanning backwards past bytes beginning with 10.
- A byte beginning with
0 is a lone ASCII character. A byte beginning with 11 starts a multi-byte character. A byte beginning with 10 is a continuation.
- Practical consequences you will meet.
- English text costs about 1 byte per character. Latin letters with accents, Greek, Cyrillic, Hebrew and Arabic cost 2. Devanagari, Chinese, Japanese and Korean cost 3. Emoji and rarer historic scripts cost 4.
- So the same sentence stored in Hindi takes roughly three times the bytes of the English version, before you even count that Hindi may need more characters.
len() in Python 3 counts code points, not bytes. strlen() in C counts bytes. .length in JavaScript counts UTF-16 code units, so an emoji outside the basic plane counts as 2.
- A byte order mark, U+FEFF, encoded as the bytes
EF BB BF, sometimes appears at the start of a UTF-8 file written by Windows tools. It is legal but discouraged, and it breaks shell scripts, because the shell sees those three bytes before the #! line.
52.4 The networking reference#
52.4.1 OSI and TCP/IP side by side#
- The OSI model has seven layers. It was published as ISO/IEC 7498-1, with the first version in 1984.
- The TCP/IP model has four layers. It was written down in RFC 1122 in October 1989, by which time the protocols had already been running for years.
- OSI is the vocabulary everyone uses. TCP/IP is the thing that actually runs. Both statements are true at the same time.
| 7 |
Application |
Application |
HTTP, DNS, SSH |
| 6 |
Presentation |
Application |
TLS, JPEG, UTF-8 |
| 5 |
Session |
Application |
RPC, TLS sessions |
| 4 |
Transport |
Transport |
TCP, UDP, QUIC |
| 3 |
Network |
Internet |
IP, ICMP, IPsec |
| 2 |
Data link |
Link |
Ethernet, Wi-Fi, ARP |
| 1 |
Physical |
Link |
copper, fibre, radio |
- Three honest notes about that table.
- TLS does not fit cleanly. It sits above TCP and below HTTP, which is layer 6-ish, but it has its own handshake and session resumption, which is layer 5-ish. People argue about this. Nobody wins.
- ARP is called layer 2 by most people and layer 2.5 by pedants, because it carries layer 3 addresses inside layer 2 frames.
- QUIC, standardized in RFC 9000 in May 2021, runs on top of UDP but does transport work: streams, ordering, congestion control, encryption. In OSI terms it is layer 4 built on top of layer 4.
- Where the reader’s own failure sat: DNS worked, so layer 7 name lookup was fine. The TCP handshake never completed, so the failure was at layer 4 or below, on the path.
52.4.2 The RFC 1918 private ranges#
- RFC 1918, published February 1996, reserves three blocks that anyone may use inside a private network and that no router on the public internet will forward.
| 10.0.0.0/8 |
10.0.0.0 to 10.255.255.255 |
16,777,216 |
| 172.16.0.0/12 |
172.16.0.0 to 172.31.255.255 |
1,048,576 |
| 192.168.0.0/16 |
192.168.0.0 to 192.168.255.255 |
65,536 |
- The middle block is the one people get wrong.
172.16.0.0/12 covers 172.16.x.x through 172.31.x.x, not 172.16 through 172.16.
- That is exactly why the reader’s traceroute looked odd. Hops showing
172.31.0.17, 172.26.22.235, 172.26.14.75 and 172.16.18.33 are all private addresses inside the same /12, used by the reader’s ISP for its own core network.
- Seeing private addresses in a traceroute is normal and proves nothing about a fault. It tells you the ISP numbers its own core privately, which is ordinary practice, especially with carrier-grade NAT.
52.4.3 Reserved IPv4 ranges with their RFCs#
| 0.0.0.0/8 |
“This network” |
RFC 791 |
| 0.0.0.0/32 |
This host, this network |
RFC 1122 |
| 10.0.0.0/8 |
Private use |
RFC 1918 |
| 100.64.0.0/10 |
Shared, carrier NAT |
RFC 6598 |
| 127.0.0.0/8 |
Loopback |
RFC 1122 |
| 169.254.0.0/16 |
Link local, no DHCP |
RFC 3927 |
| 172.16.0.0/12 |
Private use |
RFC 1918 |
| 192.0.0.0/24 |
IETF assignments |
RFC 6890 |
| 192.0.0.0/29 |
DS-Lite |
RFC 6333 |
| 192.0.2.0/24 |
Docs, TEST-NET-1 |
RFC 5737 |
| 192.31.196.0/24 |
AS112 redirection |
RFC 7535 |
| 192.52.193.0/24 |
AMT relay |
RFC 7450 |
| 192.88.99.0/24 |
Old 6to4, deprecated |
RFC 7526 |
| 192.168.0.0/16 |
Private use |
RFC 1918 |
| 192.175.48.0/24 |
AS112 delegation |
RFC 7534 |
| 198.18.0.0/15 |
Benchmarking only |
RFC 2544 |
| 198.51.100.0/24 |
Docs, TEST-NET-2 |
RFC 5737 |
| 203.0.113.0/24 |
Docs, TEST-NET-3 |
RFC 5737 |
| 224.0.0.0/4 |
Multicast |
RFC 5771 |
| 240.0.0.0/4 |
Reserved, future use |
RFC 1112 |
| 255.255.255.255/32 |
Limited broadcast |
RFC 919 |
- The full list lives in the IANA IPv4 Special-Purpose Address Registry. The rows above are the ones you meet.
169.254.x.x on your own machine means DHCP failed. The machine gave itself a link-local address because no server answered. If you see it, fix DHCP before you look at anything else.
100.64.0.0/10 is the range your ISP uses when it puts many customers behind one public address. If your router’s outside address is in this range, you are behind carrier-grade NAT and you cannot accept inbound connections.
- Use
192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 in documentation and examples. They exist for that. Never invent an example address that belongs to somebody.
52.4.4 CIDR from /8 to /32#
- The prefix length is how many leading bits are the network part. The rest is the host part.
- Total addresses is 2 to the power of (32 minus the prefix). Usable hosts is that minus two, because the all-zeros address is the network itself and the all-ones address is the broadcast.
| /8 |
255.0.0.0 |
16,777,216 |
16,777,214 |
| /9 |
255.128.0.0 |
8,388,608 |
8,388,606 |
| /10 |
255.192.0.0 |
4,194,304 |
4,194,302 |
| /11 |
255.224.0.0 |
2,097,152 |
2,097,150 |
| /12 |
255.240.0.0 |
1,048,576 |
1,048,574 |
| /13 |
255.248.0.0 |
524,288 |
524,286 |
| /14 |
255.252.0.0 |
262,144 |
262,142 |
| /15 |
255.254.0.0 |
131,072 |
131,070 |
| /16 |
255.255.0.0 |
65,536 |
65,534 |
| /17 |
255.255.128.0 |
32,768 |
32,766 |
| /18 |
255.255.192.0 |
16,384 |
16,382 |
| /19 |
255.255.224.0 |
8,192 |
8,190 |
| /20 |
255.255.240.0 |
4,096 |
4,094 |
| /21 |
255.255.248.0 |
2,048 |
2,046 |
| /22 |
255.255.252.0 |
1,024 |
1,022 |
| /23 |
255.255.254.0 |
512 |
510 |
| /24 |
255.255.255.0 |
256 |
254 |
| /25 |
255.255.255.128 |
128 |
126 |
| /26 |
255.255.255.192 |
64 |
62 |
| /27 |
255.255.255.224 |
32 |
30 |
| /28 |
255.255.255.240 |
16 |
14 |
| /29 |
255.255.255.248 |
8 |
6 |
| /30 |
255.255.255.252 |
4 |
2 |
| /31 |
255.255.255.254 |
2 |
2 |
| /32 |
255.255.255.255 |
1 |
1 |
- Two special cases. A /31 has no network or broadcast address; RFC 3021, from December 2000, allows both addresses to be hosts, which is how router-to-router links are numbered. A /32 is one single address, used for loopbacks and for host routes.
- Classless Inter-Domain Routing replaced the old class A, B and C scheme. It was defined in RFC 1518 and RFC 1519 in September 1993, and was a direct response to the routing table growing faster than router memory.
52.4.5 The ports worth memorizing#
- Port numbers are 16 bits, so 0 to 65535. RFC 6335 splits them into three ranges: 0 to 1023 system, 1024 to 49151 user or registered, and 49152 to 65535 dynamic or ephemeral.
- On Linux the outgoing ephemeral range is usually 32768 to 60999. On macOS it is 49152 to 65535. Both are settings, not standards.
| 20, 21 |
FTP data, control |
plaintext, obsolete |
| 22 |
SSH, SFTP, scp |
git over SSH |
| 23 |
Telnet |
plaintext, do not use |
| 25 |
SMTP relay |
often blocked at home |
| 53 |
DNS |
UDP and TCP |
| 67, 68 |
DHCP server, client |
UDP |
| 69 |
TFTP |
UDP, no auth |
| 80 |
HTTP |
plaintext |
| 110 |
POP3 |
plaintext |
| 123 |
NTP |
UDP |
| 143 |
IMAP |
plaintext |
| 161, 162 |
SNMP, SNMP trap |
UDP |
| 179 |
BGP |
over TCP |
| 389 |
LDAP |
plaintext |
| 443 |
HTTPS, and HTTP/3 |
TCP and UDP |
| 445 |
SMB file sharing |
never expose |
| 465 |
SMTP over TLS |
submission |
| 514 |
syslog |
UDP |
| 587 |
SMTP submission |
with STARTTLS |
| 636 |
LDAPS |
over TLS |
| 993 |
IMAPS |
over TLS |
| 995 |
POP3S |
over TLS |
| 1080 |
SOCKS proxy |
|
| 1194 |
OpenVPN |
UDP usually |
| 1433 |
Microsoft SQL Server |
|
| 1521 |
Oracle database |
|
| 3306 |
MySQL, MariaDB |
|
| 3389 |
RDP, remote desktop |
never expose |
| 5432 |
PostgreSQL |
|
| 5672 |
AMQP, RabbitMQ |
|
| 5900 |
VNC |
|
| 6379 |
Redis |
no auth by default |
| 6443 |
Kubernetes API server |
|
| 8080, 8000 |
HTTP alternate |
dev convention |
| 8443 |
HTTPS alternate |
dev convention |
| 9042 |
Cassandra |
|
| 9090 |
Prometheus |
|
| 9200 |
Elasticsearch |
|
| 11211 |
Memcached |
|
| 27017 |
MongoDB |
|
| 51820 |
WireGuard |
UDP |
- Note the two rows that mattered in the reader’s session. Git over SSH uses port 22. Git over HTTPS uses port 443. In the reader’s session one transport worked while the other did not, which is only possible because they are different ports to different endpoints.
52.4.6 ICMP types worth knowing#
| 0 |
Echo Reply |
a successful ping |
| 3 |
Destination Unreachable |
see the code below |
| 5 |
Redirect |
router says use another |
| 8 |
Echo Request |
the ping you sent |
| 11 |
Time Exceeded |
every traceroute hop |
| 12 |
Parameter Problem |
malformed header |
- Type 3 carries a code that says exactly what was unreachable.
| 0 |
Network unreachable |
| 1 |
Host unreachable |
| 2 |
Protocol unreachable |
| 3 |
Port unreachable |
| 4 |
Fragmentation needed, DF set |
| 13 |
Administratively prohibited |
- Code 4 is the one that causes mysterious hangs. It is how path MTU discovery works. If a firewall blocks it, large packets vanish and small ones get through, and a page loads its HTML but never its images.
- Code 13 is a firewall openly telling you it refused. That is a polite refusal, and it is evidence. Silence is not.
- Traceroute works entirely on type 11 code 0. Each probe goes out with a time-to-live one higher than the last, and each router that decrements it to zero sends back type 11.
- Under IPv6, ICMPv6 is defined by RFC 4443 and the numbers change. Echo Request is 128, Echo Reply is 129, Packet Too Big is 2. ICMPv6 is not optional: blocking it breaks IPv6 entirely, because Neighbor Discovery rides on types 133 to 137.
- The reader’s trace showed
* * * from hop 13 to hop 20. That means no type 11 replies came back from those hops. It does not mean the packets died there. Many routers rate-limit or refuse to generate ICMP.
52.4.7 HTTP methods#
- Safe means the method is not supposed to change anything on the server.
- Idempotent means doing it twice has the same effect as doing it once.
- Both properties are promises made by the specification, currently RFC 9110, published June 2022. A server can break them. A well-behaved one does not.
| GET |
yes |
yes |
fetch a resource |
| HEAD |
yes |
yes |
headers only |
| OPTIONS |
yes |
yes |
what is allowed |
| TRACE |
yes |
yes |
echo the request |
| PUT |
no |
yes |
replace at this URL |
| DELETE |
no |
yes |
remove |
| POST |
no |
no |
create or act |
| PATCH |
no |
no |
partial update |
| CONNECT |
no |
no |
tunnel, used by proxies |
- The practical consequence: a client may retry a GET, HEAD, PUT or DELETE automatically after a network failure. It must not retry a POST automatically, because it cannot know whether the first one landed.
- That is exactly the reasoning behind the git failure in section 52.8. A disconnect while reading the response tells you nothing about whether the write was applied.
52.4.8 HTTP status codes#
- The first digit gives the family. One hundred series is informational, two hundred success, three hundred redirection, four hundred client error, five hundred server error.
- Codes marked with a star are ones you should know without looking up.
| 100 |
Continue |
|
| 101 |
Switching Protocols |
star |
| 102 |
Processing |
|
| 103 |
Early Hints |
|
| 200 |
OK |
star |
| 201 |
Created |
star |
| 202 |
Accepted |
|
| 203 |
Non-Authoritative Info |
|
| 204 |
No Content |
star |
| 205 |
Reset Content |
|
| 206 |
Partial Content |
star |
| 207 |
Multi-Status |
|
| 208 |
Already Reported |
|
| 226 |
IM Used |
|
| 300 |
Multiple Choices |
|
| 301 |
Moved Permanently |
star |
| 302 |
Found |
star |
| 303 |
See Other |
star |
| 304 |
Not Modified |
star |
| 305 |
Use Proxy, deprecated |
|
| 307 |
Temporary Redirect |
star |
| 308 |
Permanent Redirect |
star |
| 400 |
Bad Request |
star |
| 401 |
Unauthorized |
star |
| 402 |
Payment Required |
|
| 403 |
Forbidden |
star |
| 404 |
Not Found |
star |
| 405 |
Method Not Allowed |
star |
| 406 |
Not Acceptable |
|
| 407 |
Proxy Auth Required |
|
| 408 |
Request Timeout |
|
| 409 |
Conflict |
star |
| 410 |
Gone |
|
| 411 |
Length Required |
|
| 412 |
Precondition Failed |
star |
| 413 |
Content Too Large |
|
| 414 |
URI Too Long |
|
| 415 |
Unsupported Media Type |
|
| 416 |
Range Not Satisfiable |
|
| 417 |
Expectation Failed |
|
| 418 |
I am a teapot |
|
| 421 |
Misdirected Request |
|
| 422 |
Unprocessable Content |
star |
| 423 |
Locked |
|
| 424 |
Failed Dependency |
|
| 425 |
Too Early |
|
| 426 |
Upgrade Required |
|
| 428 |
Precondition Required |
|
| 429 |
Too Many Requests |
star |
| 431 |
Headers Too Large |
|
| 451 |
Legal Reasons |
star |
| 500 |
Internal Server Error |
star |
| 501 |
Not Implemented |
|
| 502 |
Bad Gateway |
star |
| 503 |
Service Unavailable |
star |
| 504 |
Gateway Timeout |
star |
| 505 |
Version Not Supported |
|
| 506 |
Variant Also Negotiates |
|
| 507 |
Insufficient Storage |
|
| 508 |
Loop Detected |
|
| 510 |
Not Extended |
|
| 511 |
Network Auth Required |
star |
- Four distinctions that people get wrong every day.
- 401 versus 403. A 401 means “I do not know who you are; send credentials”. A 403 means “I know who you are and you may not”. The reader’s missing
workflow scope produced a 403-class refusal, not a 401.
- 502 versus 503 versus 504. A 502 means the gateway got a broken answer from the thing behind it. A 503 means the service is deliberately unavailable, often overloaded or in maintenance. A 504 means the thing behind it never answered in time.
- 301 versus 308, and 302 versus 307. The older pair, 301 and 302, historically allowed clients to change a POST into a GET when following. The newer pair, 307 and 308, forbid changing the method. Use 308 and 307 when you care.
- 418 is real and is a joke. It comes from RFC 2324, the Hyper Text Coffee Pot Control Protocol, published on 1 April 1998. RFC 7168 extended it to teapots in 2014. It is not part of HTTP.
- 451 is named for the novel Fahrenheit 451 and was standardized in RFC 7725 in February 2016. It means content is blocked for legal reasons.
52.4.9 Common DNS record types#
| A |
1 |
one IPv4 address |
| AAAA |
28 |
one IPv6 address |
| CNAME |
5 |
an alias to another name |
| MX |
15 |
mail server plus priority |
| NS |
2 |
authoritative name server |
| PTR |
12 |
name for an address |
| SOA |
6 |
zone parameters |
| TXT |
16 |
free text, SPF, DKIM |
| SRV |
33 |
host, port, priority |
| CAA |
257 |
who may issue certificates |
| DS |
43 |
DNSSEC delegation signer |
| DNSKEY |
48 |
DNSSEC public key |
| RRSIG |
46 |
DNSSEC signature |
| NSEC3 |
50 |
DNSSEC proof of absence |
| SVCB |
64 |
service binding |
| HTTPS |
65 |
HTTPS service binding |
| TLSA |
52 |
certificate pinning, DANE |
| SSHFP |
44 |
SSH host key fingerprint |
- A CNAME cannot exist at the zone apex, that is at
example.com itself, because the apex must also hold SOA and NS records and a CNAME forbids any other record at the same name. Providers work around this with a non-standard record they call ALIAS or ANAME. That is a vendor convention, not a standard.
- SVCB and HTTPS records were standardized in RFC 9460 in November 2023. The HTTPS record can tell a browser to use HTTP/3, and can carry the encrypted client hello parameters, before any connection is made.
- TTL is a number of seconds attached to every record. It is a request, not a guarantee. Resolvers may cache longer or shorter.
52.4.10 TCP flags and states#
- The TCP header has a flags field. The current specification is RFC 9293, from August 2022, which replaced RFC 793 from 1981.
| SYN |
Synchronize |
start a connection |
| ACK |
Acknowledge |
I received up to here |
| FIN |
Finish |
I have no more to send |
| RST |
Reset |
tear down now, refused |
| PSH |
Push |
deliver to the app now |
| URG |
Urgent |
urgent pointer valid |
| ECE |
ECN Echo |
congestion signalled |
| CWR |
Congestion Window Reduced |
I slowed down |
- The three-way handshake is SYN, then SYN plus ACK, then ACK. The four-way close is FIN, ACK, FIN, ACK.
- An RST in reply to a SYN means “nothing is listening on that port”. That is a refusal, and it is information. It arrives in milliseconds.
- No reply at all to a SYN means the packet was dropped somewhere and nobody told you. That is what the reader saw. It costs the full timeout.
| CLOSED |
no connection |
| LISTEN |
waiting for a SYN |
| SYN_SENT |
sent SYN, waiting |
| SYN_RECEIVED |
got SYN, sent SYN-ACK |
| ESTABLISHED |
data can flow |
| FIN_WAIT_1 |
we sent FIN |
| FIN_WAIT_2 |
our FIN was acked |
| CLOSE_WAIT |
they sent FIN, we have not |
| CLOSING |
both sent FIN together |
| LAST_ACK |
we sent our FIN last |
| TIME_WAIT |
waiting out stray packets |
- Two states are diagnostic on their own.
- Many sockets stuck in
CLOSE_WAIT means your own application is not calling close. That is an application bug, not a network problem.
- Many sockets in
TIME_WAIT is normal on a busy client. The specification says wait twice the maximum segment lifetime, which by the letter of the standard is four minutes. Linux uses a fixed sixty seconds and does not let you change it without recompiling.
- A connection that sits in
SYN_SENT and never leaves it is the exact signature of the reader’s fault: the SYN went out, nothing came back.
52.5 Networking command cheat sheet#
52.5.1 How to read this section#
- Every entry below gives the macOS form first, because that is the machine in the running example, then the Linux difference on the next line.
- Then two lines that most cheat sheets omit.
- Proves is the set of things that must be true for the command to have succeeded. If it passed, all of these are true.
- Does not prove is the trap. These are things people assume a pass means and that it does not mean.
- Getting this second line right is the difference between diagnosing a problem and guessing at it.
52.5.2 The commands at a glance#
ifconfig or ip addr |
your own interface |
no |
arp -a |
local link, layer 2 |
local only |
netstat -rn or ip route |
your routing table |
no |
ping |
ICMP reachability |
yes |
traceroute |
path, hop by hop |
yes |
mtr |
path plus loss over time |
yes |
dig, host, nslookup |
DNS resolution |
yes |
scutil --dns |
which resolver macOS uses |
no |
nc -vz |
one TCP port |
yes |
telnet |
one TCP port, old |
yes |
nmap |
many ports at once |
yes |
curl -v |
full request, TLS, HTTP |
yes |
wget |
file download |
yes |
openssl s_client |
TLS handshake only |
yes |
tcpdump |
actual packets on the wire |
no |
lsof -i |
which process holds a port |
no |
ss or netstat -an |
socket states |
no |
whois |
who owns a name or address |
yes |
networksetup |
macOS network settings |
no |
52.5.3 ping#
- macOS:
ping -c 5 github.com
- Linux: same, plus
-w 5 sets a total deadline in seconds.
- Useful flags, macOS:
-c count, -i interval, -s size, -t timeout in seconds for the whole run, -D set the do-not-fragment bit.
- Careful: on Linux
-t means time-to-live, not timeout. Same letter, different meaning. Linux uses -M do for do-not-fragment.
- Proves: your interface is up, your route exists, the target address is reachable by ICMP, and something at that address answers ICMP.
- Does not prove: that any TCP port is open, that the web server is running, that DNS is correct, or that the service you want works. Ping tests ICMP and only ICMP.
- A failed ping proves even less. Very many hosts and networks drop ICMP on purpose. Cloudflare, AWS load balancers and corporate firewalls commonly do. A ping that fails while
curl succeeds is completely normal.
- Path MTU test, macOS:
ping -D -s 1472 github.com. If 1472 fails and 1400 works, something on the path has a smaller MTU. Add 28 bytes of headers to get the real figure: 1472 plus 28 is 1500.
52.5.4 traceroute#
- macOS:
traceroute -n github.com
- Linux:
traceroute -n github.com, and tracepath is a simpler alternative that needs no privileges.
- Useful flags:
-n no reverse lookups so it runs fast, -m 30 maximum hops, -q 1 one probe per hop, -w 2 wait two seconds, -I use ICMP probes instead of UDP.
- macOS default probes are UDP to high port numbers. Linux default is the same. Windows
tracert uses ICMP by default. This matters, because firewalls filter the three differently.
- TCP mode is the most useful on a modern network. Linux:
traceroute -T -p 443 github.com. macOS: traceroute -P TCP -p 443 github.com.
- Proves: which routers along the path are willing to answer, in order, up to the last one that replied.
- Does not prove: that the path is symmetric, that the reply came back the same way it went, that a hop with no reply is broken, or that the last replying hop is where the trouble is.
- The reader’s trace stopped replying after hop 12,
ae106-0.rwa04.pnq20.ntwk.msn.net (104.44.20.42). That does not mean hop 12 dropped the traffic. It means hops beyond it did not send back ICMP time-exceeded messages, which is extremely common.
- Hops showing more than one address, as hops 5, 6, 8, 9 and 11 did in the reader’s trace, mean the traffic is being spread over parallel links. That is load balancing working correctly, not a fault.
52.5.5 mtr#
mtr combines ping and traceroute and keeps running, so it shows loss per hop over time.
- macOS: not installed by default. Install with
brew install mtr, then run with sudo mtr github.com because it needs raw sockets.
- Linux:
mtr github.com, often already installed.
- Report mode, for pasting into a ticket:
mtr -rwc 100 github.com. That means report, wide output, one hundred cycles.
- TCP mode:
mtr -T -P 443 github.com.
- Proves: sustained loss or latency at and beyond a specific hop.
- Does not prove: that loss shown at a middle hop is real. Routers deliberately rate-limit the ICMP replies they generate for themselves while forwarding everything else perfectly. Only loss that continues to the final hop and stays there is real loss.
- That single rule saves more wasted hours than any other line in this chapter. Read the last row, not the middle rows.
52.5.6 dig, host and nslookup#
dig is the precise one. Use it.
- macOS:
dig github.com. On recent macOS versions Apple has removed the BIND lookup tools from the base install. If dig is missing, get it with brew install bind.
- Linux:
dig github.com, from the dnsutils or bind-utils package.
dig +short github.com # just the answer
dig +short A github.com # only IPv4
dig +short AAAA github.com # only IPv6
dig @1.1.1.1 github.com # ask a specific resolver
dig @8.8.8.8 github.com # ask a different one
dig +trace github.com # walk from the root down
dig -x 20.207.73.82 # reverse lookup
dig +noall +answer github.com # answer section only
dig NS github.com # nameservers
dig MX github.com # mail servers
dig TXT github.com # text records
dig +tcp github.com # force TCP not UDP
dig +dnssec github.com # ask for signatures
- Proves: that a resolver answered, what it answered, and how long the answer may be cached.
- Does not prove: that the address is reachable, that it is the right address for you, or that your operating system will use it.
dig talks to the resolver directly and bypasses the system cache.
- That last point matters on macOS, which has its own resolver daemon. To see what the system will actually do, use
dscacheutil -q host -a name github.com, and to see which resolvers are configured use scutil --dns.
host github.com gives a one-line answer. nslookup github.com 1.1.1.1 works everywhere including Windows. Both are fine for a quick check and worse than dig for anything else.
- In the reader’s session, name resolution worked:
github.com resolved to 20.207.73.82 through the Cloudflare resolver at 1.1.1.1. That single fact eliminated DNS as the cause.
52.5.7 curl#
- The single most useful network command on any machine.
- macOS and Linux syntax are the same. macOS has shipped curl since 10.4.
curl -v https://github.com # show the whole conversation
curl -I https://github.com # headers only, HEAD request
curl -sS -o /dev/null -w '%{http_code}\n' https://github.com
curl -L https://example.com # follow redirects
curl --max-time 10 https://github.com # give up after 10 seconds
curl --connect-timeout 3 https://x.io # give up connecting after 3
curl -4 https://github.com # force IPv4
curl -6 https://github.com # force IPv6
curl --resolve github.com:443:20.207.73.82 https://github.com
curl -H 'Accept: application/json' https://api.github.com
curl -X POST -d 'a=1' https://example.com/f
curl -k https://self-signed.example # skip cert check
curl --http1.1 https://github.com # force HTTP/1.1
curl -x http://proxy:3128 https://x.io # go via a proxy
- The timing breakdown is the hidden gem. It tells you which stage was slow.
curl -sS -o /dev/null -w \
'dns %{time_namelookup} conn %{time_connect} tls
%{time_appconnect} first %{time_starttransfer} total
%{time_total}\n' https://github.com
- Proves, when it returns a status code: DNS resolved, TCP connected, TLS completed if the URL was HTTPS, the request was sent, and a server answered.
- Does not prove: that the answer is correct, that the site works in a browser, or that the certificate is trusted if you passed
-k.
- A hang with no output after
Trying <address>:443... proves the TCP handshake never completed. That is a layer 4 or below problem and nothing above it can be at fault yet.
- That is exactly what the reader saw.
curl -v https://github.com printed Trying 20.207.73.82:443... and then produced nothing for fifteen seconds. No RST, no ICMP unreachable, no TLS error. Silence.
- Silence is a specific finding. A server that is down sends RST. A firewall that refuses politely sends ICMP type 3 code 13. Nothing at all means a silent drop somewhere on the path.
52.5.8 wget#
- macOS: not installed by default.
brew install wget.
- Linux: usually present.
wget https://example.com/file.tar.gz # save with its own name
wget -O out.bin https://example.com/f # save under a chosen name
wget -O - https://example.com # print to standard output
wget --spider https://example.com # exists? do not save
wget -c https://example.com/big.iso # continue a partial download
wget -T 10 -t 3 https://example.com # 10s timeout, 3 tries
- Proves and does not prove: the same as curl.
- Use wget for downloading files and mirroring. Use curl for inspecting a request. That split is a convention, not a rule.
52.5.9 nc, the netcat tool#
- The cleanest way to test one TCP port and nothing else.
- macOS:
nc -vz github.com 443. Apple ships its own build of the OpenBSD version.
- Linux:
nc -vz -w 3 github.com 443 with the OpenBSD build.
nc -vz github.com 443 # is port 443 open
nc -vz -w 3 github.com 22 # with a 3 second timeout
nc -vzu 1.1.1.1 53 # UDP, far less reliable
nc -l 8080 # listen on 8080, a fake server
nc -vz github.com 20-25 # a small range
- Proves, on success: the full TCP three-way handshake completed to that address on that port.
- Does not prove: that the service behind the port speaks the protocol you expect, that TLS will succeed, or that your credentials are valid.
- This is the correct tool for separating “the network is blocked” from “the application is refusing me”. If
nc -vz succeeds and curl fails, the problem is above layer 4.
- UDP testing with
-u is close to useless on its own, because a silent UDP port and an open UDP port look identical. Only the application reply proves anything.
52.5.10 telnet#
- Removed from macOS since High Sierra, version 10.13, in 2017. Install with
brew install telnet or, better, use nc instead.
telnet github.com 443 then type text. Historically used to speak HTTP by hand.
- Proves: a TCP connection opened, and you can type raw bytes into it.
- Does not prove: anything about encryption. Telnet is plaintext. Never use the telnet protocol itself, on port 23, for real work. It sends passwords in the clear. SSH replaced it in 1995.
52.5.11 nmap#
- Not installed by default anywhere.
brew install nmap on macOS, apt install nmap on Debian and Ubuntu.
nmap -Pn -p 443 github.com # one port, skip host discovery
nmap -p 22,80,443 192.168.0.1 # a few ports
nmap -p- 192.168.0.1 # all 65535 ports, slow
nmap -sV -p 443 example.com # try to identify the service
nmap -sn 192.168.0.0/24 # who is on my network
nmap --top-ports 100 192.168.0.1 # the common ones only
sudo nmap -sS -p 443 example.com # half-open scan, needs root
- Proves: which of the scanned ports answered, and how.
- Does not prove: that a filtered result means blocked by the target. It may be blocked anywhere along the path, including by your own network.
- One legal and social rule, and it is not optional. Scan only machines you own or have written permission to scan. Port scanning other people’s networks is, in many countries including India under the Information Technology Act 2000, potentially an offence, and at minimum it will get your connection blocked.
52.5.12 netstat, ss and lsof#
- These look at your own machine, not the network. None of them need connectivity.
- macOS:
netstat -an -p tcp shows all TCP sockets. netstat -rn shows the routing table. netstat -i shows interface counters. netstat -s shows protocol statistics.
- Linux:
netstat is deprecated in favour of ss, from the iproute2 package.
ss -tulpn # Linux: listening TCP+UDP, pids
ss -tn state established # Linux: established TCP only
ss -s # Linux: summary counts
netstat -an -p tcp # macOS: all TCP sockets
netstat -rn # both: routing table
lsof -i -P -n # both: sockets with process names
lsof -nP -iTCP:443 # both: who is using port 443
lsof -nP -iTCP -sTCP:LISTEN # both: what is listening
- Proves: exactly which local process holds which port, and what state each connection is in.
- Does not prove: anything about the remote end. A socket shown as
ESTABLISHED locally can be dead, if the peer vanished without sending FIN or RST. Only traffic proves liveness.
- On macOS,
lsof needs sudo to see other users’ processes.
52.5.13 arp#
- macOS and Linux:
arp -a lists the address resolution cache, which maps IPv4 addresses to hardware addresses on your local network segment.
- Linux modern form:
ip neigh show.
sudo arp -d 192.168.0.1 deletes one entry, which forces a fresh lookup.
- Proves: which machines your computer has recently talked to on the same local segment, and their MAC addresses.
- Does not prove: anything beyond your own network. ARP does not cross a router. If the gateway is in the table with a MAC address, your link to the router is working.
arp -a showing 192.168.0.1 with a valid MAC address would have been the first thing to check in the reader’s case, and it would have passed. The local network was fine.
52.5.14 ifconfig, ip and route#
- macOS:
ifconfig and ifconfig en0. Apple has not adopted the ip command.
- Linux:
ip addr show, or the short form ip -br a.
ifconfig -a # macOS: every interface
ifconfig en0 # macOS: just Wi-Fi
ipconfig getifaddr en0 # macOS: just the IPv4 address
ipconfig getpacket en0 # macOS: the whole DHCP lease
netstat -rn # macOS: routing table
route -n get default # macOS: which route wins
ip -br a # Linux: brief addresses
ip link show # Linux: layer 2 state
ip route show # Linux: routing table
ip route get 20.207.73.82 # Linux: which route wins
- On macOS,
en0 is usually Wi-Fi on a laptop and Ethernet on a desktop. lo0 is loopback. utun0 and upwards are tunnel interfaces, created by VPNs, by iCloud Private Relay, and by some corporate agents.
- The reader’s machine showed several
utun interfaces. That is worth noticing but does not prove a VPN was routing the traffic. Check with route -n get default and see which interface the default route uses.
- Proves: your own addresses, your own routes, your own link state.
- Does not prove: that the route leads anywhere.
52.5.15 tcpdump#
- The ground truth. Everything else is an interpretation; this is the packets.
- macOS:
sudo tcpdump -i en0 -n host 20.207.73.82
- Linux: same, with the interface usually named
eth0, ens3 or wlan0.
sudo tcpdump -i en0 -n host 20.207.73.82
sudo tcpdump -i en0 -nn port 443
sudo tcpdump -i any -n icmp
sudo tcpdump -i en0 -n -c 20 tcp port 443
sudo tcpdump -i en0 -w capture.pcap # save for Wireshark
sudo tcpdump -i en0 -n 'tcp[tcpflags] & tcp-syn != 0'
sudo tcpdump -i en0 -n -A port 80 # show payload as text
- Flags that matter:
-n no name lookups, -nn also no port names, -i any all interfaces on Linux, -c N stop after N packets, -s 0 full packet, -w file write a capture file, -A print payload as ASCII, -X print hex and ASCII.
- Proves: exactly what left your machine and exactly what came back.
- Does not prove: what happened in the middle. You see your end of the wire and nothing else.
- In the reader’s case, tcpdump would have shown repeated SYN packets going out to
20.207.73.82 on port 443, with retransmissions at roughly one, three and seven seconds, and nothing at all coming back. That is the signature of a silent drop, and it is different from every other failure.
52.5.16 openssl s_client#
- Tests the TLS handshake by itself, with nothing above it.
openssl s_client -connect github.com:443 -servername github.com
openssl s_client -connect github.com:443 -showcerts
openssl s_client -connect github.com:443 -tls1_2
openssl s_client -connect github.com:443 -tls1_3
echo | openssl s_client -connect github.com:443 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
-servername sets Server Name Indication, which almost every modern host requires. Without it you may get the wrong certificate or a failure.
- Proves: TCP connected, a TLS handshake completed, which version and cipher were agreed, and which certificate chain was presented.
- Does not prove: that the certificate is trusted by your operating system. The
openssl command uses its own trust store, which on macOS is usually not the system keychain. A certificate that openssl rejects may work fine in Safari, and the reverse.
- Check expiry dates with the last command above. Certificate expiry is one of the most common causes of a site that worked yesterday and not today.
- Since 2024 the industry has been shortening maximum certificate lifetimes. The CA/Browser Forum voted in April 2025 to reduce the maximum from 398 days down in stages, reaching 47 days by March 2029. Expiry checks are getting more important, not less.
52.5.17 whois#
whois github.com looks up who registered a domain name.
whois 20.207.73.82 looks up who owns an address block.
- Proves: what a registry says. Registry data is often stale, and since the European General Data Protection Regulation took effect in May 2018, most personal contact details in domain records are hidden.
- Does not prove: who is operating the machine today.
- In the reader’s session,
whois on 20.207.73.82 would show a Microsoft range. That is consistent with GitHub being owned by Microsoft, which acquired it in 2018 for 7.5 billion dollars in stock, and fronting traffic through Microsoft’s network edge.
- The traceroute agreed: from hop 7 onward the names all ended in
ntwk.msn.net, which is Microsoft’s backbone naming.
52.5.18 macOS-only tools#
scutil --dns shows every resolver the system will use, in order, with the search domains and the interface each is bound to. This is the truth about macOS DNS; /etc/resolv.conf on macOS is a compatibility file and can be misleading.
scutil --nwi shows network information: which interfaces are usable, and whether IPv4 and IPv6 are available on each.
scutil --proxy shows the proxy configuration.
networksetup -listallnetworkservices lists the named services such as Wi-Fi and Ethernet.
networksetup -getinfo "Wi-Fi" shows address, mask, router and MAC.
networksetup -getdnsservers "Wi-Fi" shows configured resolvers.
sudo networksetup -setdnsservers "Wi-Fi" 1.1.1.1 8.8.8.8 sets them.
sudo networksetup -setdnsservers "Wi-Fi" Empty puts it back to DHCP.
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder clears the DNS cache. Both halves are needed on modern macOS.
networkQuality runs Apple’s built-in throughput and responsiveness test. It has shipped since macOS 12 Monterey, released in 2021.
ipconfig getpacket en0 prints the entire DHCP lease including the offered DNS servers, which is how you tell what the router wanted you to use.
- The reader was using
1.1.1.1, Cloudflare’s public resolver, rather than the router. scutil --dns is what proves that.
52.5.19 Worked example: the reader’s own five commands#
- Here is the exact sequence that settled the reader’s case, with what each step eliminated.
Step 1 ping 192.168.0.1
-> replies. Local network and router: fine.
Step 2 dig +short github.com
-> 20.207.73.82. DNS: fine.
Step 3 curl -v --max-time 15 https://github.com
-> "Trying 20.207.73.82:443..." then nothing.
-> TCP handshake never completed.
Step 4 traceroute -n github.com
-> reaches Microsoft's Pune edge, then * * *.
-> path exists at least that far.
Step 5 same URL on mobile data, same phone
-> loads instantly.
-> the server is up; the destination is fine.
- What that combination proves: the failure is specific to the path between this home connection and that destination, at the transport layer, and it is a silent drop rather than a refusal.
- What it does not prove: who is dropping the packets, or why. The evidence does not name a party, and you should not either.
52.6 Decision tree: I cannot reach a site#
52.6.1 The shape of the procedure#
- Work upward through the layers. Never start in the middle.
- Each step either passes, which eliminates everything below it, or fails, which tells you exactly where to stop looking.
- Do not skip a step because you are sure it is fine. The reader’s case looked like a DNS problem and was not.
START: "I cannot reach a site"
|
1. Is any other site reachable?
no -> your machine or link. Go to A.
yes v
2. Does the name resolve?
no -> DNS. Go to B.
yes v
3. Does TCP connect on the port?
no -> path or firewall. Go to C.
yes v
4. Does TLS complete?
no -> certificate or version. Go to D.
yes v
5. Does HTTP return a status code?
no -> the app is hung. Go to E.
yes v
6. Is the status what you expected?
no -> application layer. Go to F.
yes -> it works. The problem is elsewhere.
Go to G.
52.6.2 Step 1: is anything reachable at all#
- Run
ping -c 3 192.168.0.1, using your own gateway address.
- Find your gateway with
route -n get default on macOS or ip route show on Linux.
- If the gateway does not answer, stop. The problem is your machine, your cable, or your Wi-Fi. Check
ifconfig en0 for an address.
- If your address begins
169.254., DHCP failed. Nothing else matters until that is fixed. Renew the lease or restart the interface.
- If the gateway answers, run
ping -c 3 1.1.1.1.
- If a public address answers but names do not resolve, jump straight to step 2. That combination is the classic DNS-only failure.
- If the gateway answers and
1.1.1.1 does not, the problem is between your router and the internet. Check the router’s own status page and the physical line.
- Branch A conclusion: local. Fix the interface, the lease, the cable or the router before going further.
52.6.3 Step 2: does the name resolve#
- Run
dig +short github.com.
- If you get an address, DNS works. Note the address and move on.
- If you get nothing, ask a different resolver:
dig @1.1.1.1 +short github.com and dig @8.8.8.8 +short github.com.
- If a public resolver answers and your configured one does not, your resolver is the problem. On macOS, check with
scutil --dns and change it with networksetup -setdnsservers.
- If no resolver answers for that one name but others resolve, the domain itself may be broken. Check with
dig +trace github.com, which walks from the root servers down and shows where the chain stops.
- If the answer looks wrong, for example a private address for a public site, suspect a captive portal, a filtering resolver, or a modified hosts file. Check
/etc/hosts on macOS and Linux.
- Clear the cache and try again: on macOS,
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder.
- Branch B conclusion: DNS. Nothing above this layer can work until a name turns into an address.
52.6.4 Step 3: does TCP connect#
- Run
nc -vz github.com 443.
- Success means the three-way handshake completed. Move to step 4.
- Now read the failure carefully, because the three failure modes mean completely different things.
- Connection refused, immediately. You received a TCP RST. Something is there and it said no. The host is up; the port is closed. Suspect a service that is not running, or the wrong port.
- No route to host or host unreachable, quickly. You received an ICMP type 3. A router along the way openly told you it could not deliver. Suspect routing or a firewall that refuses politely.
- Timeout, after many seconds, with no message at all. Nothing came back. This is a silent drop. It is the hardest case and it is the reader’s case.
- Confirm which of the three you have with
curl -v --max-time 15 and, if you can, with sudo tcpdump -i en0 -n host <address> and port 443 in another window.
- If it is a timeout, test whether it is that one destination or all destinations: try
nc -vz 1.1.1.1 443 and nc -vz google.com 443.
- Test whether it is that one port: try
nc -vz github.com 22 and nc -vz github.com 80.
- Test whether it is that one network path: use a different network. Mobile data on a phone, with tethering or with the same URL typed into the phone, is the cleanest test available to a home user.
- If it works on a different network and not on yours, the destination is fine and the path is not.
- Branch C conclusion: transport or path. See section 52.6.8 for what to do next, because this is where the reader ended up.
52.6.5 Step 4: does TLS complete#
- Run
openssl s_client -connect github.com:443 -servername github.com.
- Success prints a certificate chain and a line saying which protocol version and cipher were negotiated.
certificate verify failed means the chain did not validate. Look at the reason code printed on the same line.
certificate has expired means exactly that. Check the dates with openssl x509 -noout -dates.
unable to get local issuer certificate usually means an intermediate certificate is missing from the server’s chain, or your trust store is old.
wrong version number or no protocols available means a version mismatch. Old servers that only speak TLS 1.0 are refused by modern clients. Test with -tls1_2 and -tls1_3 explicitly.
- A hostname mismatch means the certificate does not cover the name you asked for. Check the subject alternative names with
openssl x509 -noout -text and look for the SAN extension.
- If TLS fails only in your browser but succeeds with openssl, suspect a corporate inspection proxy that is re-signing traffic, or a VPN.
- Branch D conclusion: TLS. The network is fine; the trust is not.
52.6.6 Step 5 and 6: does HTTP answer, and correctly#
- Run
curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' against the URL.
- No status code and a long time means the connection opened and the server never replied. That is an application hang, not a network fault. Branch E.
- A 502, 503 or 504 means you reached a gateway and the thing behind it is broken or overloaded. That is the operator’s problem, not yours. Branch F.
- A 401 or 403 means you reached the right service and it refused you. Check your credentials, token, or scopes. Branch F.
- A 404 means you reached the right service and asked for the wrong path.
- A 429 means you are being rate limited. Read the
Retry-After header.
- A 200 with wrong content means caching, a stale CDN copy, or the wrong virtual host. Try
curl -H 'Cache-Control: no-cache' and try the origin directly with --resolve.
- Branch F conclusion: application layer. The whole network stack worked.
52.6.7 Everything passes and it still fails#
- This happens, and it has a small number of causes. Work through them in this order.
- It works from the command line but not in the browser. Suspect a browser extension, a proxy setting inside the browser, or a service worker holding a cached broken response. Test in a private window with extensions off.
- It works over IPv4 and not IPv6, or the reverse. Force each with
curl -4 and curl -6. Broken IPv6 with working IPv4 is common and produces long pauses because the browser tries IPv6 first.
- The reader’s machine reported
IPv6: (none), which removes this branch from the reader’s own case entirely.
- It works for small requests and fails for large ones. That is an MTU problem. Test with
ping -D -s 1472 and step the size down. If 1400 works and 1472 does not, something on the path has a smaller MTU and is not sending the ICMP message that would let path MTU discovery work.
- It works sometimes. Run
mtr -rwc 200 and look at the loss on the final row only. Intermittent means loss, congestion, or a load-balanced path where one of several parallel links is broken.
- That last case is worth naming: if a hop shows several addresses, as hops 5, 6, 8, 9 and 11 did in the reader’s trace, then some flows take one link and some take another. A single bad link out of four gives you a fault that appears for one in four connections and looks random.
- It works for other people on the same network. Compare
scutil --dns, route -n get default and the proxy settings between the two machines.
- It stopped working at a specific time. Ask what changed then: a certificate expiry, a DNS TTL running out, a scheduled firewall rule, a router reboot.
- It works with a VPN on. That proves the destination and the server are fine and that the direct path is the problem, because the VPN carries your traffic through a different route. It does not prove who is blocking.
52.6.8 Exactly what to report to an ISP#
- Support staff cannot act on “the internet is not working”. They can act on a reproducible measurement. Give them these seven things, in this order.
- Your own public address and the time, including the time zone. Get the address with
curl -4 ifconfig.me when you have any working path, or from the router’s status page.
- The destination name and the exact address it resolves to on your machine. For the reader:
github.com resolving to 20.207.73.82.
- The exact symptom, in protocol terms. Not “it is slow”. Say: “TCP SYN to 20.207.73.82 port 443 receives no response of any kind, no RST and no ICMP unreachable, and times out after 15 seconds.”
- A full traceroute, pasted verbatim, with
-n so it is fast and unambiguous. Include the hop where replies stop.
- The control test that proves it is not the destination. For the reader: “The same URL loads instantly over mobile data on the same phone, at the same moment.”
- The control test that proves it is not everything. For example: “Other HTTPS sites work normally from the same machine at the same time.”
- An
mtr report over at least 100 cycles, in report mode, so they can see loss over time rather than one snapshot.
- What you have already ruled out, listed plainly: local network, DNS, the destination server, IPv6.
- One sentence of what you want: “Please check whether traffic from my connection to 20.207.73.82 on port 443 is being dropped in your network.”
- Do not accuse anyone. The evidence supports a silent drop somewhere on that path. It does not identify who is doing it, and claiming otherwise makes the ticket easier to dismiss.
- Keep a copy of every command output with timestamps. If the fault is intermittent, that record is the only thing that will get it taken seriously.
52.7 Git command cheat sheet#
52.7.1 How to read the two extra columns#
- Net says whether the command needs the network. If it says no, the command works with the cable unplugged. That is most of git.
- Risk says whether the command can destroy work that is not recoverable from the reflog.
- Low means safe. Medium means it rewrites history but the old commits stay reachable through
git reflog for 90 days by default. High means it can delete uncommitted changes or untracked files, which nothing can recover.
52.7.2 Setup and configuration#
git init |
make a new repository |
no |
low |
git init --bare |
make a server-side repo |
no |
low |
git clone URL |
copy a repo and history |
yes |
low |
git clone --mirror |
exact copy of all refs |
yes |
low |
git clone --depth 1 |
shallow, latest commit |
yes |
low |
git config --global x y |
set a user-wide setting |
no |
low |
git config --list |
show every setting |
no |
low |
git config --list --show-origin |
and which file set it |
no |
low |
git remote -v |
list remotes and URLs |
no |
low |
git remote add name URL |
add a remote |
no |
low |
git remote set-url name URL |
change a remote URL |
no |
low |
git help command |
full documentation |
no |
low |
- The three settings worth doing on a new machine, once:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
git config --global pull.rebase false
git config --global core.editor "vim"
52.7.3 Local work#
git status |
what changed, what is staged |
no |
low |
git status -sb |
the same, one line each |
no |
low |
git add file |
stage a file |
no |
low |
git add -p |
stage chosen hunks |
no |
low |
git add -A |
stage everything |
no |
low |
git commit -m "msg" |
record the staged snapshot |
no |
low |
git commit --amend |
replace the last commit |
no |
med |
git diff |
working tree vs index |
no |
low |
git diff --staged |
index vs last commit |
no |
low |
git diff main..feature |
between two branches |
no |
low |
git rm file |
delete and stage the delete |
no |
high |
git mv old new |
rename and stage it |
no |
low |
git restore file |
discard working changes |
no |
high |
git restore --staged file |
unstage, keep changes |
no |
low |
git stash |
park changes, clean tree |
no |
low |
git stash pop |
bring them back |
no |
low |
git stash list |
see what is parked |
no |
low |
git clean -n |
show what would be deleted |
no |
low |
git clean -fd |
delete untracked files |
no |
high |
git restore file and git clean -fd are the two most dangerous commands in daily use. They delete things git has never seen. The reflog cannot help you. Always run git clean -n first.
52.7.4 History and inspection#
git log |
full history |
no |
low |
git log --oneline |
one line per commit |
no |
low |
git log --oneline --graph --all |
the branch shape |
no |
low |
git log -p |
history with diffs |
no |
low |
git log -S "text" |
commits that changed text |
no |
low |
git log --since="2 weeks" |
by date |
no |
low |
git show SHA |
one commit in full |
no |
low |
git blame file |
who last touched each line |
no |
low |
git shortlog -sn |
commits per author |
no |
low |
git describe --tags |
nearest tag plus distance |
no |
low |
git rev-list --count HEAD |
how many commits |
no |
low |
git bisect start |
begin a binary search |
no |
low |
git bisect good SHA |
mark a known-good point |
no |
low |
git bisect bad |
mark the current as bad |
no |
low |
git bisect reset |
stop and go back |
no |
low |
git reflog |
every position HEAD held |
no |
low |
git range-diff a...b |
compare two versions of a series |
no |
low |
git reflog is the undo button for almost everything. If a rebase or reset went wrong, find the old SHA in the reflog and git reset --hard to it. Entries expire after 90 days for reachable commits and 30 days for unreachable ones, by default.
52.7.5 Branching#
git branch |
list local branches |
no |
low |
git branch -a |
include remote-tracking |
no |
low |
git branch -vv |
show tracking and ahead/behind |
no |
low |
git switch name |
move to a branch |
no |
low |
git switch -c name |
create and move to it |
no |
low |
git switch - |
back to the previous branch |
no |
low |
git checkout name |
the older form of switch |
no |
low |
git branch -d name |
delete a merged branch |
no |
low |
git branch -D name |
delete regardless |
no |
med |
git merge feature |
join a branch in |
no |
low |
git merge --abort |
undo a conflicted merge |
no |
low |
git merge --no-ff |
always make a merge commit |
no |
low |
git rebase main |
replay commits on a new base |
no |
med |
git rebase -i HEAD~5 |
edit the last five commits |
no |
med |
git rebase --abort |
undo an in-progress rebase |
no |
low |
git cherry-pick SHA |
copy one commit here |
no |
low |
git merge-base a b |
the common ancestor |
no |
low |
git tag -a v1.0 -m "msg" |
make an annotated tag |
no |
low |
git worktree add ../dir br |
second checkout, same repo |
no |
low |
- The one fact about rebase that matters. Rebase does not move commits. It creates new commit objects with the same changes and new parents, so every rebased commit gets a new SHA.
- That is exactly what the reader saw: a local branch rebased onto a moved
main came out with a different SHA, because it was a different object.
- Merge preserves history and adds a commit. Rebase rewrites history and adds none. Neither is correct in general; teams pick one by convention.
52.7.6 Remotes and the network#
git fetch |
download objects and refs |
yes |
low |
git fetch --all --prune |
all remotes, drop dead refs |
yes |
low |
git pull |
fetch then merge |
yes |
low |
git pull --rebase |
fetch then rebase |
yes |
med |
git push |
send commits to a remote |
yes |
low |
git push -u origin br |
push and set upstream |
yes |
low |
git push --force |
overwrite the remote branch |
yes |
high |
git push --force-with-lease |
force, but refuse if it moved |
yes |
med |
git push --tags |
send tags too |
yes |
low |
git ls-remote origin |
list remote refs, live |
yes |
low |
git remote show origin |
remote details |
yes |
low |
git remote update |
fetch all remotes |
yes |
low |
git remote prune origin |
delete stale tracking refs |
yes |
low |
git bundle create f.bundle --all |
history in one file |
no |
low |
git archive HEAD -o src.tar |
export files, no history |
no |
low |
git submodule update --init |
fetch nested repos |
yes |
low |
- Only the rows marked yes need the network. Everything in 52.7.3, 52.7.4, 52.7.5 and 52.7.7 works offline, because the whole history is already in
.git.
- That is why the reader could keep committing to branches during a total outage and push them later. Commits are local writes to local files.
git ls-remote origin is the only way to learn the true current state of a remote. git rev-parse origin/main reads a cached value from your last fetch.
- In the reader’s session,
origin/main showed 0a95cc8 during the outage. That was the remote-tracking ref, which is a note about the past, not a live fact.
- Never use
--force on a shared branch. Use --force-with-lease, which checks that the remote is still where you last saw it and refuses if somebody else pushed in the meantime.
52.7.7 Undoing#
git restore --staged file |
unstage, keep the change |
no |
low |
git restore file |
throw away the change |
no |
high |
git reset --soft HEAD~1 |
undo commit, keep staged |
no |
med |
git reset --mixed HEAD~1 |
undo commit, keep in tree |
no |
med |
git reset --hard HEAD~1 |
undo commit, delete changes |
no |
high |
git reset --hard origin/main |
make local match remote |
no |
high |
git revert SHA |
new commit that undoes one |
no |
low |
git commit --amend |
fix the last commit |
no |
med |
git reflog then reset --hard |
recover a lost position |
no |
med |
git checkout SHA -- file |
one file from a past commit |
no |
high |
git clean -fdx |
delete untracked and ignored |
no |
high |
- The rule that keeps you safe:
revert is for history that other people have. reset is for history only you have.
--soft keeps everything staged. --mixed, the default, keeps changes in the working tree but unstaged. --hard deletes them.
52.7.8 Plumbing, for looking inside#
git cat-file -t SHA |
the object’s type |
no |
low |
git cat-file -p SHA |
the object’s contents |
no |
low |
git cat-file -s SHA |
the object’s size |
no |
low |
git hash-object -w file |
store a blob, print its SHA |
no |
low |
git rev-parse HEAD |
resolve a name to a SHA |
no |
low |
git rev-parse --short HEAD |
the short form |
no |
low |
git rev-parse --git-dir |
where .git actually is |
no |
low |
git ls-tree HEAD |
one tree object listed |
no |
low |
git ls-files -s |
the index, with SHAs |
no |
low |
git write-tree |
index becomes a tree object |
no |
low |
git commit-tree tree -p par |
build a commit by hand |
no |
low |
git update-ref refs/heads/x SHA |
point a ref somewhere |
no |
med |
git symbolic-ref HEAD |
what HEAD points at |
no |
low |
git show-ref |
every ref and its SHA |
no |
low |
git for-each-ref |
refs, formatted |
no |
low |
git fsck --full |
check every object |
no |
low |
git gc |
pack and prune |
no |
low |
git count-objects -v |
how many objects, how big |
no |
low |
git verify-pack -v file.idx |
inside a pack file |
no |
low |
git check-ignore -v file |
which rule ignores it |
no |
low |
- The four object types, seen with
git cat-file -t: blob is file contents, tree is a directory listing, commit is a snapshot plus metadata, tag is an annotated tag.
- Git stores content addressed by hash. Git 2.29, released October 2020, added experimental SHA-256 repositories; SHA-1 remains the default in 2026.
52.8 Decision tree: my git operation failed#
52.8.1 The five families#
- Every git failure belongs to one of five families. Naming the family is 90 per cent of the fix.
| Transport |
before or during the bytes |
usually |
| Authentication |
who are you |
after fixing |
| Permission or scope |
you, but not allowed |
after fixing |
| Server-side rejection |
server said no, on purpose |
after fixing |
| Local |
never left your machine |
yes |
git command failed. Read the FIRST error line.
|
Does it mention a host, port, TLS,
timeout, disconnect, or reset?
yes -> TRANSPORT. See 52.8.3
no v
Does it say Authentication failed,
Permission denied (publickey),
or ask for a password repeatedly?
yes -> AUTHENTICATION. See 52.8.4
no v
Does it name a scope, a permission,
or say 403 / not authorized?
yes -> SCOPE. See 52.8.5
no v
Does it say rejected, non-fast-forward,
protected branch, hook declined, or
pre-receive?
yes -> SERVER REJECTION. See 52.8.6
no -> LOCAL. See 52.8.7
52.8.2 First, always: did it actually happen#
- Before diagnosing anything, find out the current true state.
- Run
git ls-remote origin. That is a live query. It is the only reliable answer.
- Compare with
git rev-parse HEAD locally.
- Do not trust
git rev-parse origin/main or git status after a failure. Those read remote-tracking refs, which are a cached memory of your last successful fetch.
- This step is not optional. Half of all git panic is caused by acting on a stale local belief about the remote.
52.8.3 Transport failures#
- Symptoms:
Could not resolve host, Failed to connect, Connection timed out, Connection reset by peer, SSL_ERROR_SYSCALL, RPC failed, early EOF, unexpected disconnect while reading sideband packet.
- Diagnostics, in order:
git ls-remote origin # does anything work at all
ssh -T git@github.com # SSH transport, port 22
nc -vz github.com 22 # is port 22 even reachable
nc -vz github.com 443 # is port 443 reachable
curl -sS -o /dev/null -w '%{http_code}\n' \
https://github.com
GIT_CURL_VERBOSE=1 git fetch # verbose HTTPS
GIT_TRACE=1 GIT_TRACE_PACKET=1 git fetch
- Retry: usually safe for fetch, clone and pull, because they only read.
- Retry for push: safe only after you have re-queried with
git ls-remote and know whether the previous attempt landed.
- Common fixes. If port 22 is blocked but 443 is not, use SSH over 443 by putting this in
~/.ssh/config:
Host github.com
Hostname ssh.github.com
Port 443
User git
- If large pushes fail but small ones work, raise the buffer:
git config --global http.postBuffer 524288000. This is a workaround, not a fix, and it helps only with HTTP transport.
- If HTTPS fails and SSH works, or the reverse, that is not a git problem at all. It is two different ports to two different endpoints, and one path is broken. The reader saw exactly this in the same session.
52.8.4 Worked branch: sideband packet disconnect#
- The exact error, seen twice in the reader’s session:
send-pack: unexpected disconnect while reading sideband packet.
- What it means, literally. Git pushed the pack of objects, then waited on the sideband channel for the server’s progress and status messages, and the connection died while it was reading them.
- Now the important part. The disconnect happened while reading the response. It says nothing about whether the write was applied.
- Three outcomes are all consistent with that one message. The server never received the pack. The server received it and rejected it. The server received it, applied it, and the acknowledgement was lost on the way back.
- This is the same problem as a POST over HTTP that times out. The request is not idempotent, so the client cannot safely assume anything.
- The only correct next step is to ask the server what it now believes:
git ls-remote origin
git ls-remote origin refs/heads/main
git rev-parse HEAD
- If the remote SHA equals your local SHA, the push landed. Do nothing.
- If it does not, push again. A repeated push of the same commits is harmless, because git will only send objects the server lacks.
- In the reader’s case, both times the operation had not been applied. That was established by re-querying, not by reading the error.
- Retry safety: safe to retry, but only after checking. Never force-push as a response to this error. Force is how a lost acknowledgement turns into lost commits.
52.8.5 Authentication and scope failures#
- Authentication symptoms:
Permission denied (publickey), Authentication failed, repeated password prompts, Invalid username or password.
ssh -vT git@github.com # verbose SSH handshake
ssh-add -l # keys the agent holds
ssh-add ~/.ssh/id_ed25519 # load a key
git config --get remote.origin.url
git credential-osxkeychain erase # macOS, then type host lines
- GitHub stopped accepting account passwords for git over HTTPS on 13 August
- If something is prompting you for a password, it wants a personal access token, not your password.
- Scope symptoms are different and more specific. The push authenticates fine and is then refused with a named reason.
- Worked branch, from the reader’s session. A push was refused because the token lacked the
workflow scope, and the push modified a file under .github/workflows/.
- That is not a bug. It is a deliberate rule: a token that can push code should not be able to change what runs automatically in CI, because CI has its own secrets and its own privileges.
- The diagnosis is the error text plus one question: does this push touch a workflow file? Check with
git diff --stat origin/main..HEAD and look for .github/workflows/.
- The fix is to add the
workflow scope to the token, or to move the workflow change into a separate push made with a token that has it.
- Retry safety: retrying with the same token will fail identically, every time. Nothing was applied, because the server refused before applying.
- This is a clean example of the difference between 401 and 403 thinking. You were identified successfully. You were then found to lack a permission.
- The general rule for tokens: check three things, in this order. Is it expired. Does it cover this repository. Does it have the scope this specific action needs.
52.8.6 Server-side rejections#
- Symptoms:
! [rejected], non-fast-forward, Updates were rejected because the remote contains work that you do not have locally, protected branch, pre-receive hook declined, GH006.
- A non-fast-forward rejection means somebody pushed since your last fetch. The server is protecting their work.
git fetch origin
git log --oneline HEAD..origin/main # what they added
git log --oneline origin/main..HEAD # what you added
git rebase origin/main # or: git merge origin/main
git push
- A protected-branch rejection means the repository’s rules forbid a direct push. Open a pull request instead. This is a setting, not an error.
- A
pre-receive hook declined means the server ran a script that said no. The reason is usually in the lines just after. Common causes are commit message format rules, file size limits, and secret scanning.
- GitHub blocks any single file over 100 MiB, and warns above 50 MiB. That limit has been in place since 2015 and is a service policy, not a git limit. Use Git Large File Storage for big files.
- Retry safety: retrying unchanged always fails again. Nothing was applied. Fix the cause first.
52.8.7 Local failures#
- Symptoms:
not a git repository, pathspec did not match any file, your local changes would be overwritten, You have unmerged paths, index.lock exists, detached HEAD.
git rev-parse --git-dir # am I in a repo at all
git status # the honest current state
git diff --name-only --diff-filter=U # conflicted files
rm -f .git/index.lock # only if no git is running
git fsck --full # object integrity
git switch - # leave detached HEAD safely
index.lock exists almost always means a previous git process died or an editor’s git integration is running. Check with ps aux | grep git before deleting the lock file.
detached HEAD is not an error. It means HEAD points at a commit rather than a branch. Commits made there are reachable only from the reflog, so create a branch before you go further: git switch -c rescue.
- Retry safety: always safe. Nothing left your machine.
- Finally, one distinction that matters and that the reader’s session showed clearly. Git the tool and GitHub the service are different things. During the outage plain git kept working locally, and the GitHub web API did not. An error from
gh or from the API is not a git error.
52.8.8 Verifying CI properly#
- A green tick next to a branch name means “some run for something on this branch passed”. It does not say which commit.
- The strong check is by run identifier against the exact head SHA:
git rev-parse HEAD
gh run list --branch main --limit 5
gh run view <run-id> --json headSha,status,conclusion
- Compare the
headSha field with your local SHA character by character. If they differ, the tick is about a different commit.
- Merge state, seen through the API, uses values such as
CLEAN, BLOCKED and UNKNOWN. UNKNOWN usually means the server has not finished computing the merge yet; re-query rather than assuming a failure.
52.9 Shell and system cheat sheet#
52.9.1 Files and directories#
pwd |
print the current directory |
cd - |
go back to the previous one |
ls -lah |
long, all, human sizes |
ls -lt |
newest first |
ls -lS |
biggest first |
mkdir -p a/b/c |
make the whole path |
cp -r src dst |
copy a directory |
cp -a src dst |
copy, keep all attributes |
mv old new |
move or rename |
rm -i file |
delete, asking first |
rm -rf dir |
delete a tree, no questions |
ln -s target link |
make a symbolic link |
touch file |
create empty, or update time |
stat file |
size, times, permissions |
file thing |
guess the format |
du -sh dir |
total size of a directory |
du -sh * \| sort -h |
biggest items first |
df -h |
free space per filesystem |
basename path |
last part of a path |
dirname path |
everything but the last part |
52.9.2 Looking inside files#
cat file |
print the whole file |
less file |
page through it, q to quit |
head -n 20 file |
first 20 lines |
tail -n 20 file |
last 20 lines |
tail -f log |
follow as it grows |
wc -l file |
count lines |
sort file |
sort lines |
sort -u |
sort and remove duplicates |
uniq -c |
count runs, needs sort first |
cut -d, -f2 file |
second comma field |
tr 'a-z' 'A-Z' |
change characters |
sed 's/old/new/g' |
replace text |
awk '{print $3}' |
print the third field |
grep -n pattern file |
matching lines, numbered |
grep -ri pattern dir |
recursive, ignore case |
grep -v pattern |
lines that do not match |
grep -c pattern |
just the count |
diff -u a b |
unified difference |
xxd file \| head |
hex dump |
jq '.field' file.json |
read JSON properly |
52.9.3 Finding things#
find . -name '*.log' # by name
find . -type f -size +100M # files over 100 MB
find . -mtime -1 # changed in the last day
find . -name '*.tmp' -delete # find and delete
find . -type f -exec grep -l x {} + # search inside
which python3 # which one runs
type -a python3 # every match, and aliases
command -v python3 # portable form
locate filename # from an index, if built
52.9.4 Processes and resources#
ps aux |
every process |
ps aux \| grep name |
find one |
top |
live view |
htop |
nicer live view, install it |
kill PID |
ask politely, signal 15 |
kill -9 PID |
force, signal 9 |
pkill -f pattern |
kill by command line |
jobs |
this shell’s background jobs |
bg and fg |
move a job |
nohup cmd & |
keep running after logout |
time cmd |
how long it took |
uptime |
load averages |
free -h |
memory, Linux only |
vm_stat |
memory, macOS |
sysctl -n hw.ncpu |
core count, macOS |
nproc |
core count, Linux |
uname -a |
kernel and machine |
sw_vers |
macOS version |
lsb_release -a |
Linux distribution |
- Signal 15, SIGTERM, asks a program to shut down cleanly. Signal 9, SIGKILL, cannot be caught or ignored and gives the program no chance to save. Try 15 first, always.
52.9.5 Archives, permissions and transfer#
tar -czf out.tar.gz dir/ # create, gzip
tar -xzf in.tar.gz # extract
tar -tzf in.tar.gz # list without extracting
zip -r out.zip dir/ # create a zip
unzip in.zip # extract a zip
gzip file # replaces file with file.gz
gunzip file.gz # and back
chmod 644 file # rw for owner, r for others
chmod 755 script # add execute for everyone
chmod +x script # just add execute
chown user:group file # change ownership
umask # default permission mask
rsync -avz src/ host:dst/ # copy, resume, compress
scp file host:/path # simple copy over SSH
ssh -i key user@host # connect with a key
- Permission digits are octal. Read is 4, write is 2, execute is 1. Add them. The three digits are owner, group, others. So 755 is read-write-execute for the owner and read-execute for everyone else.
52.9.6 Keyboard shortcuts in the shell#
| Ctrl-C |
interrupt the running command |
| Ctrl-D |
end of input, or log out |
| Ctrl-Z |
suspend, resume with fg |
| Ctrl-L |
clear the screen |
| Ctrl-A |
jump to start of line |
| Ctrl-E |
jump to end of line |
| Ctrl-U |
delete to start of line |
| Ctrl-K |
delete to end of line |
| Ctrl-W |
delete the word before |
| Ctrl-Y |
paste what you just deleted |
| Alt-B, Alt-F |
back or forward one word |
| Ctrl-R |
search command history |
| Ctrl-G |
leave the history search |
| Up, Down |
previous, next command |
| Tab |
complete a name |
| Tab Tab |
show all completions |
!! |
the previous command |
!$ |
last argument of it |
sudo !! |
rerun the last one as root |
- On macOS Terminal, Alt is the Option key, and you may need to enable “Use Option as Meta key” in the settings before Alt-B and Alt-F work.
52.9.7 The shell expansion order#
- The shell rewrites your command line in a fixed order before running anything. Knowing the order explains almost every strange quoting bug.
1. brace expansion {a,b} -> a b
2. tilde expansion ~ -> /Users/you
3. parameter expansion $VAR -> its value
4. arithmetic expansion $((2+2)) -> 4
5. command substitution $(cmd) -> its output
6. word splitting on spaces, tabs, newlines
7. pathname expansion *.txt -> matching files
8. quote removal "x" -> x
- The two consequences that bite people.
- Word splitting happens after variable expansion. So an unquoted
$FILE holding my report.txt becomes two arguments. Quoting it stops that.
- Pathname expansion happens after variable expansion too, so a variable containing
* will expand to filenames unless you quote it.
52.9.8 The safety rules#
- Quote every variable. Write
"$FILE", not $FILE. Write "$@", not $@. This one habit prevents most shell bugs.
- Start scripts with strict mode.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-e exits on the first failing command. -u treats an unset variable as an error. -o pipefail makes a pipeline fail if any stage fails, not just the last. Setting IFS stops word splitting on plain spaces.
- Never pipe an unknown URL into a shell.
curl ... | sh hands a remote server the ability to run anything as you. Download first, read it, then run it.
- There is a further reason beyond trust: a server can serve different bytes to
curl piped into a shell than to curl writing a file, because it can see the download stall as the shell executes. Reading the file you downloaded is the only real protection.
- Test destructive commands first. Use
git clean -n, rm -i, rsync --dry-run, and find ... -print before find ... -delete.
- Never run
rm -rf with a variable that might be empty. If $DIR is unset, rm -rf "$DIR"/ becomes rm -rf /. set -u prevents this.
- Use
sudo for one command, not for a session. A root shell left open is a mistake waiting for a typing error.
- Prefer absolute paths in scripts and cron jobs. The working directory and the
PATH in an automated context are not the ones you have interactively.
52.10 File format identification by magic number#
- A magic number is a fixed byte sequence at a known offset that identifies a file’s format. The extension is a hint; the magic number is the fact.
- Check with
file thing for a guess, or xxd file | head -2 for the bytes.
| PNG image |
89 50 4E 47 0D 0A 1A 0A |
.png |
| JPEG image |
FF D8 FF |
.jpg |
| GIF image |
47 49 46 38 |
.gif |
| BMP image |
42 4D |
.bmp |
| TIFF, little-endian |
49 49 2A 00 |
.tif |
| TIFF, big-endian |
4D 4D 00 2A |
.tif |
| WebP |
52 49 46 46 then WEBP |
.webp |
| ICO icon |
00 00 01 00 |
.ico |
| PSD, Photoshop |
38 42 50 53 |
.psd |
| PDF document |
25 50 44 46 2D |
.pdf |
| PostScript |
25 21 |
.ps |
| RTF document |
7B 5C 72 74 66 |
.rtf |
| ZIP, and docx, xlsx, jar |
50 4B 03 04 |
.zip |
| GZIP |
1F 8B |
.gz |
| BZIP2 |
42 5A 68 |
.bz2 |
| XZ |
FD 37 7A 58 5A 00 |
.xz |
| Zstandard |
28 B5 2F FD |
.zst |
| 7-Zip |
37 7A BC AF 27 1C |
.7z |
| RAR version 5 |
52 61 72 21 1A 07 01 |
.rar |
| TAR |
75 73 74 61 72 at byte 257 |
.tar |
| Old Office, OLE2 |
D0 CF 11 E0 A1 B1 1A E1 |
.doc |
| MP3 with ID3 tag |
49 44 33 |
.mp3 |
| FLAC audio |
66 4C 61 43 |
.flac |
| OGG container |
4F 67 67 53 |
.ogg |
| WAV audio |
52 49 46 46 then WAVE |
.wav |
| MP4, HEIC, AVIF |
66 74 79 70 at byte 4 |
.mp4 |
| Matroska, WebM |
1A 45 DF A3 |
.mkv |
| ELF executable |
7F 45 4C 46 |
none |
| Mach-O 64-bit |
CF FA ED FE |
none |
| Windows PE, EXE |
4D 5A |
.exe |
| Java class file |
CA FE BA BE |
.class |
| WebAssembly |
00 61 73 6D |
.wasm |
| SQLite 3 database |
53 51 4C 69 74 65 20 66 |
.sqlite |
| Git pack file |
50 41 43 4B |
.pack |
| Git index file |
44 49 52 43 |
none |
| Debian package |
21 3C 61 72 63 68 3E |
.deb |
| RPM package |
ED AB EE DB |
.rpm |
| Shell script |
23 21 |
.sh |
| PEM certificate |
2D 2D 2D 2D 2D 42 45 47 |
.pem |
| ISO 9660 image |
43 44 30 30 31 at 0x8001 |
.iso |
| WOFF web font |
77 4F 46 46 |
.woff |
| OpenType font |
4F 54 54 4F |
.otf |
- Two entries in that table are the same bytes, and this is a real trap.
CA FE BA BE is both a Java class file and an old Mach-O universal binary header. The disambiguation is what follows.
50 4B 03 04 covers a very large family. Modern Office files, Java jar files, EPUB books, Android APK files and OpenDocument files are all zip archives with different contents inside.
- Two other useful facts.
MZ at the start of a Windows executable stands for Mark Zbikowski, one of the original MS-DOS developers, and has been there since 1983. 7F 45 4C 46 is a non-printable byte followed by the letters ELF, deliberately chosen so text tools do not mangle it.
52.11 Complexity and performance reference#
52.11.1 Big-O and what fits in one second#
- Big-O describes how the work grows as the input grows. It hides constant factors on purpose.
- The table assumes a rough budget of 100 million simple operations per second, which is a fair figure for interpreted-to-compiled code across the range. Compiled C can do ten times more; Python often ten times less.
| O(1) |
constant |
any size |
| O(log n) |
logarithmic |
effectively unlimited |
| O(n) |
linear |
100,000,000 |
| O(n log n) |
linearithmic |
about 4,500,000 |
| O(n^2) |
quadratic |
about 10,000 |
| O(n^3) |
cubic |
about 464 |
| O(2^n) |
exponential |
about 26 |
| O(n!) |
factorial |
about 11 |
- The practical reading. If n is a million, you need O(n) or O(n log n). If n is ten thousand, O(n^2) is acceptable. If n is twenty, anything works.
- Constants matter in real code. A cache-friendly O(n^2) can beat a pointer-chasing O(n log n) for small n. Measure before optimizing.
52.11.2 Data structure operation costs#
| Array, by index |
O(1) |
O(n) |
| Array, by value |
O(n) |
O(n) |
| Dynamic array, append |
O(1) |
O(1) amortized |
| Linked list |
O(n) |
O(1) at a known node |
| Hash table |
O(1) average |
O(1) average |
| Hash table, worst case |
O(n) |
O(n) |
| Balanced tree |
O(log n) |
O(log n) |
| Binary heap, minimum |
O(1) |
O(log n) |
| Trie |
O(k), k is key length |
O(k) |
| B-tree |
O(log n) |
O(log n) |
| Skip list |
O(log n) expected |
O(log n) expected |
| Bloom filter |
O(k) hashes |
insert only |
- A hash table is O(1) on average and O(n) in the worst case, when every key lands in the same bucket. Modern languages randomize the hash seed at startup to stop attackers forcing that deliberately.
- A B-tree is the shape used by databases and filesystems because its nodes are sized to a disk block, so one comparison level costs one read.
- A Bloom filter can say “definitely not present” or “probably present”. It never gives a false negative and cannot delete.
52.11.3 Sorting algorithms#
| Quicksort |
O(n log n) |
O(n^2) |
in place, unstable |
| Mergesort |
O(n log n) |
O(n log n) |
needs O(n) space, stable |
| Heapsort |
O(n log n) |
O(n log n) |
in place, unstable |
| Timsort |
O(n log n) |
O(n log n) |
stable, fast on runs |
| Introsort |
O(n log n) |
O(n log n) |
quicksort with fallback |
| Insertion sort |
O(n^2) |
O(n^2) |
O(n) if nearly sorted |
| Selection sort |
O(n^2) |
O(n^2) |
fewest swaps |
| Bubble sort |
O(n^2) |
O(n^2) |
teaching only |
| Counting sort |
O(n + k) |
O(n + k) |
small integer keys |
| Radix sort |
O(n k) |
O(n k) |
not comparison based |
- Stable means two records with equal keys keep their original order. That matters when you sort by one column and then another.
- Timsort was written by Tim Peters in 2002 for Python. Java adopted it for objects in Java 7, released 2011. It finds runs that are already sorted and merges them, which is why it is O(n) on nearly sorted data.
- Introsort, by David Musser in 1997, is what most C++ standard libraries use for
std::sort. It runs quicksort and switches to heapsort if the recursion gets too deep, which removes the O(n^2) worst case.
- No comparison sort can beat O(n log n) in the worst case. That is a proved lower bound, not an engineering limit. Counting and radix sort beat it only because they do not compare.
52.11.4 The cost of common operations#
| Integer add or subtract |
under 1 ns |
| Integer multiply |
1 to 3 ns |
| Integer divide |
6 to 13 ns |
| Float add or multiply |
1 to 2 ns |
| Float divide |
4 to 5 ns |
| Square root |
about 5 ns |
| Function call |
1 to 2 ns |
| Virtual or indirect call |
2 to 5 ns |
| Branch, predicted correctly |
under 1 ns |
| Branch, mispredicted |
3 to 5 ns |
| Uncontended mutex |
about 17 ns |
| Contended atomic increment |
50 to 100 ns |
| Hash a short string |
20 to 50 ns |
| Small malloc and free |
50 to 100 ns |
| A simple system call |
100 to 500 ns |
| Thread context switch |
1 to 5 us |
| Create a thread |
10 to 30 us |
| Fork and exec a process |
0.5 to 1 ms |
| One Python bytecode step |
30 to 50 ns |
| One Python function call |
about 100 ns |
- System calls got more expensive in 2018, when the Meltdown and Spectre mitigations forced page table isolation. Depending on the machine and the mitigation set, a simple system call can cost several times what it did before.
- The gap between a Python operation and a C operation is roughly fifty to a hundred times. That is why numerical Python work is done inside NumPy, where the loop lives in C.
52.12 Cloud and infrastructure reference#
52.12.1 Service equivalence#
| Virtual machines |
EC2 |
Virtual Machines |
Compute Engine |
| Object storage |
S3 |
Blob Storage |
Cloud Storage |
| Block storage |
EBS |
Managed Disks |
Persistent Disk |
| Shared file storage |
EFS |
Azure Files |
Filestore |
| Archive storage |
S3 Glacier |
Archive Storage |
Archive |
| Managed relational |
RDS |
Azure SQL Database |
Cloud SQL |
| Scale-out relational |
Aurora |
SQL Hyperscale |
Spanner |
| Managed NoSQL |
DynamoDB |
Cosmos DB |
Firestore |
| Wide-column store |
Keyspaces |
Cosmos DB |
Bigtable |
| Data warehouse |
Redshift |
Synapse, Fabric |
BigQuery |
| In-memory cache |
ElastiCache |
Azure Cache |
Memorystore |
| Serverless functions |
Lambda |
Functions |
Cloud Run functions |
| Containers, managed |
ECS, Fargate |
Container Apps |
Cloud Run |
| Kubernetes |
EKS |
AKS |
GKE |
| Container registry |
ECR |
Container Registry |
Artifact Registry |
| Load balancing |
ELB, ALB, NLB |
Load Balancer |
Cloud Load Balancing |
| Content delivery |
CloudFront |
Front Door |
Cloud CDN |
| DNS |
Route 53 |
Azure DNS |
Cloud DNS |
| Private network |
VPC |
Virtual Network |
VPC |
| Identity |
IAM |
Entra ID |
IAM |
| Secrets |
Secrets Manager |
Key Vault |
Secret Manager |
| Message queue |
SQS |
Service Bus |
Pub/Sub |
| Publish and subscribe |
SNS |
Event Grid |
Pub/Sub |
| Event streaming |
Kinesis |
Event Hubs |
Pub/Sub |
| Managed Kafka |
MSK |
Event Hubs |
Managed Kafka |
| Workflow orchestration |
Step Functions |
Logic Apps |
Workflows |
| Monitoring and logs |
CloudWatch |
Azure Monitor |
Cloud Monitoring |
| Infrastructure as code |
CloudFormation |
Bicep, ARM |
Deployment Manager |
| Machine learning |
SageMaker |
Azure ML |
Vertex AI |
| Hosted large models |
Bedrock |
Azure AI Foundry |
Vertex AI |
- Names change. Microsoft renamed Azure Active Directory to Microsoft Entra ID in 2023, and has been folding Synapse into Microsoft Fabric since 2023. Google renamed Cloud Functions to Cloud Run functions in 2024.
- The equivalences are approximate. Spanner and Aurora are not the same thing; both solve “a relational database bigger than one machine” and solve it differently.
52.12.2 The service model responsibility split#
| On-premises |
everything |
nothing |
| IaaS |
OS upward |
hardware, network, power |
| PaaS |
code and data |
OS, runtime, scaling |
| Serverless |
function and data |
everything else |
| SaaS |
your data and users |
the whole application |
- One rule survives every model: you are always responsible for your own data and your own access control. No provider takes that on.
- The shared responsibility model is the standard phrase. AWS states it as “security of the cloud” being theirs and “security in the cloud” being yours.
- The most common real breach in cloud is not a provider failure. It is a storage bucket left open to the public, which is entirely on the customer side of the line.
52.12.3 Instance families and what they are for#
| t |
burstable, cheap |
dev, small sites |
| m |
balanced |
general servers |
| c |
CPU per unit cost |
encoding, simulation |
| r |
memory |
caches, in-memory DBs |
| x, z |
very large memory |
SAP, huge datasets |
| i, d |
local NVMe storage |
databases, search |
| p |
GPU for training |
model training |
| g |
GPU for inference |
inference, graphics |
| inf, trn |
custom AI silicon |
inference, training |
- The letters above are the AWS convention. Azure uses series letters such as D for general purpose, F for compute, E and M for memory, and N for GPU. Google uses names such as N2, C3, M3 and A3.
- A modern name such as
m7g.2xlarge reads as: family m, generation 7, Graviton Arm processor, size 2xlarge. The suffix g for Arm is an AWS convention.
- Size doubling is a convention too. Each step up in size usually doubles vCPU and memory and doubles the price, so cost per unit of work is roughly flat. That is why splitting across smaller instances rarely saves money by itself.
52.12.4 Cost dimensions to check on any bill#
- Compute time, per hour or per second, per instance.
- Storage, per gigabyte-month, separately for each class.
- Data transfer out to the internet, per gigabyte. This is the item that surprises people. Ingress is usually free; egress is not.
- Data transfer between availability zones and between regions, per gigabyte, in both directions.
- Request counts. Object storage charges per PUT and per GET, and a workload with many tiny objects can cost more in requests than in storage.
- Provisioned throughput and IOPS on disks and databases, whether or not you use them.
- Load balancer hours plus a per-capacity-unit charge.
- NAT gateway hours plus a per-gigabyte processing charge. This is a common source of unexplained cost in private networks.
- Public IPv4 addresses. AWS began charging for every public IPv4 address on 1 February 2024, at 0.005 US dollars per hour, which is about 3.60 dollars a month each.
- Snapshots and backups, which keep growing and which nobody deletes.
- Log ingestion and log retention, charged separately.
- Idle waste: unattached volumes, stopped instances still holding disks, old snapshots, forgotten test environments.
- Support plan, usually a percentage of the total spend.
- Commitment discounts. Reserved instances and savings plans trade flexibility for a lower rate. Spot instances are much cheaper and can be taken away with two minutes of notice.
52.13 Security reference#
52.13.1 Hash functions, as of 2026#
| MD5 |
broken |
collisions since 2004 |
| SHA-1 |
broken |
collision proved 2017 |
| SHA-256 |
use |
the default choice |
| SHA-384, SHA-512 |
use |
wider, often faster on 64-bit |
| SHA-3 family |
use |
different internal design |
| BLAKE2, BLAKE3 |
use |
very fast, not FIPS |
| CRC32 |
not a hash |
error detection only |
- MD5 was broken by Xiaoyun Wang and colleagues in 2004. SHA-1 was broken in practice on 23 February 2017, when Google and CWI Amsterdam published two different PDF files with the same SHA-1 digest, an attack they named SHAttered. A chosen-prefix collision followed in January 2020.
- Neither is safe for signatures or integrity against an attacker. Both are still acceptable as non-security checksums, and git still uses SHA-1 with an added collision-detection check.
52.13.2 Ciphers and key exchange, as of 2026#
| Symmetric encryption |
AES-256-GCM |
DES, 3DES, RC4 |
| Symmetric, no AES hardware |
ChaCha20-Poly1305 |
AES in ECB mode |
| Mode of operation |
GCM, or GCM-SIV |
CBC without a MAC |
| Key exchange |
X25519, ECDHE |
static RSA, small DH |
| Signature |
Ed25519, ECDSA P-256 |
DSA, RSA-1024 |
| RSA, if you must |
3072 bits or more |
1024, 2048 for new keys |
| SSH host and user keys |
Ed25519 |
DSA, RSA-1024 |
- NIST disallowed three-key Triple DES for new encryption after 31 December
- RC4 was prohibited in TLS by RFC 7465 in February 2015.
- Post-quantum standards arrived on 13 August 2024, when NIST published FIPS 203 for ML-KEM, derived from Kyber, FIPS 204 for ML-DSA, derived from Dilithium, and FIPS 205 for SLH-DSA, derived from SPHINCS+.
- Deployment is hybrid: browsers and servers have been negotiating X25519 combined with ML-KEM-768 since 2024, so a future quantum computer cannot decrypt traffic captured today. This is established practice now, not a research claim.
52.13.3 Password storage parameters#
- Never store a password. Store a slow, salted hash of it. These are the OWASP recommended minimums.
| Argon2id, preferred |
m=19456 KiB, t=2, p=1 |
| Argon2id, alternative |
m=47104 KiB, t=1, p=1 |
| Argon2id, alternative |
m=12288 KiB, t=3, p=1 |
| scrypt |
N=2^17, r=8, p=1 |
| scrypt, alternative |
N=2^16, r=8, p=2 |
| bcrypt |
work factor 10 or more |
| PBKDF2-HMAC-SHA256 |
600,000 iterations |
| PBKDF2-HMAC-SHA512 |
220,000 iterations |
m is memory in kibibytes, t is the number of passes, p is parallelism. Memory cost is the point: it is what makes custom hardware attacks expensive.
- bcrypt silently ignores everything past 72 bytes of input. Enforce a maximum length or pre-hash. The work factor is a power of two, so 12 is four times the work of 10.
- Always use a random salt per password, at least 16 bytes. A pepper, a secret stored outside the database, is optional and adds real value.
- Plain SHA-256 of a password is not acceptable, with or without a salt. It is far too fast: modern hardware computes billions per second.
52.13.4 TLS configuration essentials#
- Enable TLS 1.3 and TLS 1.2. Disable everything older. TLS 1.0 and 1.1 were formally deprecated by RFC 8996 in March 2021.
- TLS 1.3, standardized in RFC 8446 in August 2018, has only five cipher suites and all of them are safe. The three you will see are
TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384 and TLS_CHACHA20_POLY1305_SHA256.
- For TLS 1.2, allow only suites beginning
ECDHE_ with GCM or CHACHA20. That gives forward secrecy and authenticated encryption.
- Turn on OCSP stapling so clients do not have to contact the certificate authority themselves.
- Send the HTTP Strict Transport Security header with a long lifetime:
Strict-Transport-Security: max-age=31536000; includeSubDomains.
- Disable TLS compression and client-initiated renegotiation. The CRIME attack of 2012 used compression to recover session cookies.
- Publish a CAA DNS record naming which certificate authorities may issue for your domain.
- Watch expiry. The CA/Browser Forum agreed in April 2025 to reduce the maximum certificate lifetime in stages, reaching 47 days by March 2029. Automated renewal stops being optional.
52.13.5 The OWASP Top 10, 2025 release#
| A01 |
Broken Access Control |
| A02 |
Security Misconfiguration |
| A03 |
Software Supply Chain Failures |
| A04 |
Cryptographic Failures |
| A05 |
Injection |
| A06 |
Insecure Design |
| A07 |
Authentication Failures |
| A08 |
Software or Data Integrity Failures |
| A09 |
Security Logging and Alerting Failures |
| A10 |
Mishandling of Exceptional Conditions |
- This list replaced the 2021 edition. Two changes are worth knowing. Supply chain failures moved up to third place, reflecting attacks on dependencies and build systems rather than on the application itself. Mishandling of exceptional conditions is new at tenth: error handling that leaks information or fails open.
- The list is an awareness document, ordered by a mix of measured incidence and survey. It is not a checklist you can pass.
52.13.6 A personal security checklist#
- Use a password manager and a different password for every site. This one change removes the largest category of real-world account loss.
- Turn on two-factor authentication everywhere it is offered. Prefer a passkey or a hardware security key, then a time-based code app, and use SMS only if nothing else is available. SMS codes can be stolen by taking over the phone number.
- Turn on full disk encryption. On macOS that is FileVault, in System Settings under Privacy and Security.
- Let the operating system and browser update themselves.
- Use a non-administrator account for daily work where you can.
- Back up on the 3-2-1 rule: three copies, on two kinds of media, one of them off-site. Test a restore at least once; an untested backup is a guess.
- Lock the screen when you walk away.
- Review which applications have access to your accounts. On GitHub, check authorized OAuth applications and personal access tokens, and delete the ones you do not recognize.
- Give every token the smallest scope that works, and an expiry date. The reader’s
workflow scope refusal is the system working correctly.
- Check your email addresses against public breach databases once a year.
- Do not install browser extensions you do not need. An extension can read everything you see on every page.
- Be suspicious of urgency. Almost every successful phishing message manufactures a deadline.
52.14 AI and machine learning reference#
52.14.1 The memory formula#
- For inference, the base memory is simple: bytes = number of parameters times bytes per parameter.
| FP32 |
4 |
full precision |
| FP16 or BF16 |
2 |
standard for inference |
| FP8 |
1 |
newer hardware only |
| INT8 |
1 |
common quantization |
| INT4 or NF4 |
0.5 |
aggressive quantization |
- Add roughly 20 per cent on top for the runtime, activations and fragmentation.
- Then add the key-value cache, which grows with the conversation: KV bytes = 2 times layers times kv-heads times head-dim times sequence length times batch size times bytes per element.
- The leading 2 is because there is one key tensor and one value tensor.
- For training with the Adam optimizer in mixed precision, budget roughly 16 to 20 bytes per parameter: 4 for the master weights, 4 for momentum, 4 for variance, 2 for the working copy, 2 for gradients, plus activations.
- So a 7 billion parameter model needs about 14 GB to run at 16-bit and about 112 GB or more to train the same way. That gap is why fine-tuning methods such as LoRA exist.
52.14.2 Model size against hardware#
| 1 B |
0.5 GB |
1 GB |
2 GB |
| 3 B |
1.5 GB |
3 GB |
6 GB |
| 7 B |
3.5 GB |
7 GB |
14 GB |
| 8 B |
4 GB |
8 GB |
16 GB |
| 13 B |
6.5 GB |
13 GB |
26 GB |
| 32 B |
16 GB |
32 GB |
64 GB |
| 70 B |
35 GB |
70 GB |
140 GB |
| 405 B |
203 GB |
405 GB |
810 GB |
- Practical readings for 2026 hardware. A laptop with 16 GB of unified memory runs a 7B or 8B model comfortably at 4-bit. A 24 GB consumer GPU runs 13B at 8-bit or 32B at 4-bit. A 70B model at 16-bit needs two 80 GB accelerators. A 405B model needs a multi-node cluster.
- Quantization costs quality, and the cost is not linear. Going from 16-bit to 8-bit is usually almost free. Going to 4-bit is usually acceptable. Going below 4-bit degrades noticeably, though the frontier moves.
52.14.3 Token cost rules of thumb#
| 1 token, English |
about 4 characters |
| 1 token, English |
about 0.75 words |
| 1,000 words English |
about 1,300 tokens |
| 1 page of prose |
about 500 tokens |
| Source code |
about 3 chars per token |
| Hindi in Devanagari |
3 to 6 times English |
| A 300-page book |
about 170,000 tokens |
- Non-Latin scripts cost more because the tokenizer was trained mostly on English text, so it has fewer merged pieces for them. This is a measured property of specific tokenizers, not a law.
- Cost scales with tokens in plus tokens out, and output tokens are usually priced several times higher than input tokens.
- Attention cost grows with the square of the sequence length in the classical transformer, so doubling the context roughly quadruples that part of the compute.
52.14.4 Common hyperparameters#
| Learning rate |
step size when updating weights |
| Batch size |
examples per update |
| Epochs |
passes over the whole dataset |
| Warmup steps |
ramp-up before full learning rate |
| Weight decay |
penalty that keeps weights small |
| Dropout |
fraction of units randomly ignored |
| Gradient accumulation |
fake a bigger batch on small memory |
| LoRA rank |
size of the small trained adapter |
| Temperature |
randomness when sampling output |
| Top-p, nucleus |
keep tokens summing to probability p |
| Top-k |
keep only the k most likely tokens |
| Repetition penalty |
discourage repeating tokens |
| Context length |
how many tokens the model can see |
| Max new tokens |
how long the answer may be |
- Temperature 0 means always take the most likely token, which makes output nearly deterministic. Temperature 1 samples from the model’s own distribution. Above about 1.2 the output usually becomes incoherent.
- Learning rate is the hyperparameter that matters most. Too high and training diverges; too low and it never gets anywhere.
52.14.5 How to read a model announcement#
- Separate the three kinds of claim before you read anything else.
- Established fact: parameter count, context length, licence, price per million tokens, whether weights can be downloaded, release date.
- Active research: benchmark scores, reasoning ability, agentic behaviour, long-context quality. These are measured, but the measurements are contested and move quickly.
- Marketing claim: “state of the art”, “PhD-level”, “reasoning”, “understands”. These words have no agreed definition.
- Then ask these ten questions.
- Which exact benchmark, which version, and which prompting setup. A score with chain-of-thought and 64 samples is not comparable to a single-shot score.
- Was the benchmark possibly in the training data. Contamination is common and usually undetectable from outside.
- Is this a base model or an instruction-tuned model. They are not interchangeable.
- What is the licence. “Open weights” means you can download the numbers. “Open source” properly means you also get the training data and code, which is rare.
- What is the real context window, and is there an independent measurement of quality at the far end of it. Advertised context and usable context often differ.
- What is the throughput in tokens per second, and at what batch size.
- What is the price per million input tokens and per million output tokens.
- Who ran the evaluation. First-party numbers should be treated as a claim until someone independent repeats them.
- What is the cut-off date of the training data.
- What is not mentioned. Announcements omit the benchmarks where the model did badly, and that omission is information.
52.15 Regular expressions#
52.15.1 The syntax table#
. |
any one character |
a.c matches abc |
^ |
start of string or line |
^Hi |
$ |
end of string or line |
end$ |
* |
zero or more of the last |
ab* matches a |
+ |
one or more of the last |
ab+ matches ab |
? |
zero or one of the last |
colou?r |
{3} |
exactly three |
a{3} matches aaa |
{2,4} |
two to four |
a{2,4} |
{2,} |
two or more |
a{2,} |
*?, +? |
lazy, match as few as possible |
<.+?> |
[abc] |
any one listed character |
[aeiou] |
[^abc] |
any character not listed |
[^0-9] |
[a-z] |
a range |
[A-Za-z] |
\d |
a digit |
same as [0-9] |
\D |
not a digit |
|
\w |
word character |
letters, digits, underscore |
\W |
not a word character |
|
\s |
whitespace |
space, tab, newline |
\S |
not whitespace |
|
\b |
word boundary |
\bcat\b |
\B |
not a word boundary |
|
\| |
either side |
cat\|dog |
( ) |
group and capture |
(ab)+ |
(?: ) |
group without capturing |
(?:ab)+ |
(?<name> ) |
named capture group |
|
\1 |
what group 1 matched |
(\w)\1 finds doubles |
(?= ) |
followed by, lookahead |
foo(?=bar) |
(?! ) |
not followed by |
foo(?!bar) |
(?<= ) |
preceded by, lookbehind |
(?<=Rs)\d+ |
(?<! ) |
not preceded by |
|
\. |
a literal dot |
escapes the dot |
(?i) |
ignore case from here |
|
- Flags are set outside the pattern in most languages:
i ignore case, m multi-line so ^ and $ match at each line, s so . also matches a newline, g find all matches.
- Two warnings. Nested quantifiers such as
(a+)+ can take exponential time on a failing input, which is a real denial-of-service class called catastrophic backtracking. And you cannot parse HTML with a regular expression; use a parser.
52.15.2 Five patterns worth keeping#
- Match an IPv4 address, roughly:
\b(?:\d{1,3}\.){3}\d{1,3}\b
- It finds four groups of one to three digits separated by dots. It will also match
999.999.999.999, so validate the ranges afterwards in code. A regular expression that fully validates IPv4 is long and unreadable, and checking each number is under 256 in code is clearer.
- Find the start of every log line with a timestamp:
^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}
- This matches both
2026-08-13 14:05:09 and the ISO 8601 form with a T. Use it with grep -E to pick out real log lines and drop stack-trace continuation lines.
- Match a quoted string, without running past the closing quote:
"(?:[^"\\]|\\.)*"
- The trick is
[^"\\], which means any character that is not a quote and not a backslash, alternated with \\., which means a backslash followed by anything. That handles escaped quotes inside the string.
- Find a possible secret in a diff before you commit it:
(?i)(api[_-]?key|secret|token|password)\s*[:=]\s*\S+
- Run it with
git diff --staged | grep -E before every commit. It produces false positives, which is the correct trade-off for this job.
- Capture the status code and byte count from a common log format line:
"\s(\d{3})\s(\d+|-)$
- Group 1 is the HTTP status, group 2 is the response size or a dash. Pipe it through
awk or sed -E to build a count by status code.
52.16 Keyboard shortcuts and editor basics#
52.16.1 Terminal#
| Cmd-T |
new tab, macOS Terminal |
| Cmd-N |
new window, macOS |
| Cmd-K |
clear scrollback, macOS |
| Ctrl-Shift-T |
new tab, Linux terminals |
| Ctrl-Shift-C, Ctrl-Shift-V |
copy, paste, Linux |
| Ctrl-A then D |
detach from a tmux session |
| Ctrl-A then C |
new tmux window |
| Ctrl-A then N |
next tmux window |
- The tmux prefix is Ctrl-B by default; Ctrl-A is a very common reconfiguration and a convention rather than a default.
52.16.2 Shell line editing#
- The shortcuts in section 52.9.6 are Emacs-style bindings, which is the default in bash and zsh.
- If you prefer vi bindings, run
set -o vi. Then Escape puts you in command mode and the vim movement keys work on the command line.
Ctrl-R then typing searches history backwards. Press Ctrl-R again to go further back. Ctrl-G cancels.
52.16.3 Enough vim to edit a file and get out#
- vim has modes. That is the whole thing people find confusing. You start in normal mode, where letters are commands, not text.
i |
insert before the cursor |
a |
insert after the cursor |
o |
open a new line below |
Escape |
leave insert mode |
h j k l |
left, down, up, right |
0 |
start of line |
$ |
end of line |
gg |
top of file |
G |
bottom of file |
:42 |
go to line 42 |
/text then Enter |
search forward |
n |
next match |
x |
delete one character |
dd |
delete the whole line |
yy |
copy the whole line |
p |
paste after the cursor |
u |
undo |
Ctrl-R |
redo |
:w |
write the file |
:q |
quit |
:wq |
write and quit |
:q! |
quit and throw away changes |
- The five you actually need, in order: press
Escape to be sure you are in normal mode. Press i to type. Press Escape again. Type :wq and press Enter to save and leave. Type :q! and press Enter to leave without saving.
- If a git command drops you into an editor you did not expect, that is vim, because it is the default
core.editor on many systems. Change it with git config --global core.editor "nano" if you would rather.
- nano is the friendly alternative.
Ctrl-O writes, Ctrl-X exits, and it prints the shortcuts along the bottom of the screen.
52.17 What to do when you are stuck#
52.17.1 The debugging method#
- Reproduce it. A bug you cannot trigger on purpose cannot be fixed, only guessed at. Write down the exact steps.
- Read the actual error. Not the summary. The first line and the deepest line of the stack.
- Establish what is true. Write down three things you have proved and how you proved them. Write down three things you are assuming.
- Check the assumptions first. Most bugs live there. The version you think you are running, the file you think you are editing, the environment variable you think is set.
- Bisect. Cut the problem in half. Does the failure happen with half the input, half the config, half the code.
git bisect does this over history automatically.
- Change one thing at a time. Two changes at once means you learn nothing from the result.
- Look at the boundary. Most faults sit where two systems meet: your code and a library, your machine and the network, your process and the filesystem.
- Ask what changed. If it worked yesterday, something moved. A deployment, a certificate, a DNS record, a dependency update, a disk filling up.
- Explain it out loud to a person or an object. Saying it in order forces the gap into view. This is called rubber-duck debugging, and it works because it makes you state your assumptions.
- Take a break. After ninety minutes with no progress, stop. Returning with fresh attention beats another hour of the same.
- Write down the fix and why it worked. The same problem will come back to you or to somebody else.
52.17.2 How to read an error message#
- Read the first line and the last line before anything in between. The first says what went wrong. The last is often where it went wrong.
- Find the first line that mentions your own code. Everything above it is usually library internals.
- Separate the error type from the message from the location.
FileNotFoundError is the type. The path is the message. The line number is the location.
- Look for a number: an exit code, an errno, a status code, a signal number. Those are searchable and precise.
- Common exit codes: 0 success, 1 general error, 2 misuse of a shell builtin, 126 found but not executable, 127 command not found, 130 interrupted by Ctrl-C, 137 killed by signal 9, often the out-of-memory killer.
- Common errno values: 2 ENOENT no such file, 13 EACCES permission denied, 32 EPIPE broken pipe, 98 EADDRINUSE address already in use, 110 ETIMEDOUT connection timed out, 111 ECONNREFUSED connection refused.
- Note what the error does not say. An error that says “connection refused” tells you something answered. An error that says “timed out” tells you nothing answered. Those are different problems.
52.17.3 The minimal reproduction#
- Copy the failing thing into a new, empty directory or file.
- Delete half of it. Does it still fail. If yes, delete half of what is left. If no, put it back and delete the other half.
- Repeat until removing anything makes the failure go away.
- What remains is the reproduction. It is usually five to twenty lines.
- Remove your credentials, your company’s names and your real data. Replace them with
example.com and the documentation addresses from section 52.4.3.
- Half the time the bug becomes obvious during this process, and you never need to ask anyone. That is not wasted effort; that is the method working.
52.17.4 Searching effectively#
- Search the exact error text, in quotes, with your own paths, names and numbers removed.
- Add the technology and the version:
postgres 16, not postgres.
- Add the year if the technology moves fast, or restrict the results to the last year. Answers about a 2019 version of a fast-moving tool are often wrong now.
- Search the project’s own issue tracker before the general web. The people who wrote the code discuss it there.
- Read the official documentation for the exact version you are running. Not the latest, the one you have.
- When an answer says “just disable the check”, read why the check exists before doing it.
- Prefer answers that explain the mechanism. An answer that gives you a command with no reason will not help you next time, and may not be safe.
52.17.5 Asking a question that gets answered#
- Give these seven things, in this order, and you will get an answer.
- What you are trying to do, in one sentence, at the level of the goal and not the current attempt.
- What you did, as exact commands or code that someone else could run.
- What you expected to happen.
- What actually happened, pasted verbatim, including the full error, as text and not as a picture.
- Your environment: operating system and version, tool versions, and how it was installed.
- What you have already tried and what each attempt ruled out. This is the single line that most changes the quality of the answers, because it stops people suggesting things you have done.
- A minimal reproduction, from section 52.17.3.
- Two things not to do. Do not ask “does anyone know about X”; just ask the question. And do not describe the problem you invented while trying to solve the real one, without saying what the real one is; that is the most common reason a thread goes nowhere.
- If you solve it yourself before anyone replies, post the answer. Somebody with the same problem will find it.
52.98 Common wrong ideas#
- Wrong: a failed ping means the host is down. Right: it means ICMP did not come back. Very many hosts drop ICMP deliberately and serve HTTPS perfectly.
- Wrong:
* * * at the end of a traceroute proves the fault is at that hop. Right: it proves those routers did not send ICMP time-exceeded messages, which is common and often deliberate.
- Wrong: a timeout and a connection refused are both just failures. Right: refused means something answered and said no; a timeout means nothing answered at all. They point at different causes.
- Wrong: private addresses in a traceroute mean something is misconfigured. Right: ISPs number their own cores privately as normal practice, which is why the reader saw 172.31, 172.26 and 172.16 addresses.
- Wrong:
git rev-parse origin/main tells you where the remote is. Right: it tells you where the remote was at your last fetch. Only git ls-remote asks the server.
- Wrong: a disconnect during a push means the push did not happen. Right: the disconnect happened while reading the reply, so it says nothing about whether the write landed. Re-query and find out.
- Wrong: 1 GB and 1 GiB are the same. Right: they differ by 7.4 per cent, and the gap grows with every step up the scale.
- Wrong: MB/s and Mb/s are the same. Right: they differ by a factor of eight. A 100 Mbit/s line delivers about 12 MB/s at best.
- Wrong: a green tick on a branch means your commit passed CI. Right: it means some run passed for something on that branch. Check the run’s head SHA against your own.
- Wrong: hashing a password with SHA-256 is secure storage. Right: SHA-256 is far too fast. Use Argon2id, scrypt or bcrypt with the parameters in section 52.13.3.
52.99 Chapter summary in 20 lines#
- This chapter is for looking things up, not for reading through.
- Learn the latency ladder as shape, not as digits: cache in nanoseconds, memory in hundreds of nanoseconds, storage in microseconds, disks and networks in milliseconds.
- Scaled to human time at one nanosecond per second, a cross-continent round trip is several years and a fifteen-second timeout is several centuries.
- About one millisecond of round trip per hundred kilometres of fibre is a rule you can do in your head, and it is physics.
- Learn 2^10, 2^20 and 2^30, and the three that cause bugs: 2^31, 2^32 and 2^53.
- Decimal and binary prefixes differ by a percentage that grows with scale: 2.4 per cent at kilo, 7.4 at giga, 20.9 at yotta.
- One hex digit is four bits and two are one byte. That is the only reason hexadecimal exists in computing.
- UTF-8 costs one byte for English, two for most European scripts, three for Devanagari and East Asian scripts, four for emoji.
- The seven OSI layers are the vocabulary; the four TCP/IP layers are what actually runs. Both statements are true together.
- The private ranges are 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, from RFC 1918, and the middle one reaches to 172.31.
- For every command, ask two questions: what does a pass prove, and what does it not prove. That second question is the whole method.
- For “I cannot reach a site”, work upward: link, DNS, TCP, TLS, HTTP, content. Each pass eliminates everything below it.
- Refused, unreachable and silent are three different failures with three different causes, and only the third costs you a full timeout.
- Report a fault to an ISP in protocol terms with a control test, and never accuse anyone; the evidence supports a silent drop and does not name a party.
- Almost every git command works offline, because the whole history is in the
.git directory on your machine.
- Git failures fall into five families: transport, authentication, scope, server-side rejection and local. Naming the family is most of the fix.
- After any failed push, ask the server what it now believes with
git ls-remote before doing anything else.
- Quote your variables, start scripts with
set -euo pipefail, and never pipe an unknown URL into a shell.
- A magic number identifies a file; the extension only suggests one.
- When stuck: reproduce, read the real error, separate what is proved from what is assumed, change one thing at a time, and write down the fix.