Cook the pasta: Bring a large pot of salted water to a boil.
Add the macaroni and cook until just shy of al dente (about 1 minute less than package directions). Reserve 1/2 cup pasta water, then drain. Set aside.
Warm the milk: In a small saucepan or microwave-safe container, gently warm the milk until just steamy.
Don’t boil. Warm milk helps prevent lumps in the sauce.
Make the roux: In a large, wide pot over medium heat, melt the butter. Sprinkle in the flour and whisk constantly for 1–2 minutes until the mixture is smooth and smells slightly nutty.
It should stay pale; don’t brown it.
Build the sauce: Slowly pour in the warm milk while whisking. Add in 2–3 additions, whisking until smooth before adding more. Continue whisking for 4–6 minutes, until the sauce is thick enough to coat the back of a spoon.
Season the base: Whisk in Dijon mustard, garlic powder, onion powder, paprika, and a pinch of salt and pepper.
Taste; it should be well seasoned but not salty.
Add the cheese: Reduce heat to low. Add the cheddar and Gruyère in small handfuls, whisking after each addition until fully melted and smooth. Keep the sauce just below a simmer to prevent splitting.
Adjust consistency: If the sauce is too thick, whisk in a splash of milk.
If too thin, let it gently bubble for another minute to thicken. It should be glossy and pourable.
Combine with pasta: Add the drained pasta to the pot and fold with a spatula until every piece is coated. If it tightens up, add a splash of the reserved pasta water to loosen and add silkiness.
Final seasoning: Taste and adjust salt and pepper.
Serve hot, with a light dusting of paprika or extra black pepper if you like.