Despite Alvaro Morata's missed penalty at the beginning of the second half, La Roja took the lead with Aymeric Laporte's early header and continued to play confidently.
Captain Morata made it 2-0 with a lovely finish on 65 minutes and Alex Baena, who featured at the Euros before starring in the Spain team that won Olympic gold, made it 3-0 from a free-kick. Serbia finished with 10 men after defender Strahinja Pavlovic was sent off.
"We're in another final phase, which is the important thing -- it might seem easy but we're in all of these final phases in recent years, and that deserves credit," Morata told state broadcaster TVE.
Denmark are best placed to go through with Spain after they drew 2-2 away to Switzerland, who prop up the group.
The Danes twice came from behind, with Remo Freuler's opener cancelled out almost immediately by Gustav Isaksen, only for Zeki Amdouni to put the Swiss back ahead with a penalty in first-half stoppage time.
However, Christian Eriksen struck on 69 minutes to ensure Denmark left St Gallen with a point.
Advertisement
Portugal dropped points for the first time in Group A1 as they were forced to settle for a share of the spoils with Scotland at Hampden.
On his 216th international appearance, Ronaldo made his 200th start for his country, more than 21 years on from his first.
However, the 39-year-old failed to add to his 133 Portugal goals and stormed off the field at full-time ranting at the officials.
The home side needed a wonder save from goalkeeper Craig Gordon to deny Bruno Fernandes a winner for Portugal in the closing stages, but held out to secure their first point in the top tier of the Nations League.
Advertisement
That allowed them to end a run of four straight competitive defeats, but they have now won just once -- in a friendly against Gibraltar -- in their last 16 matches.
Croatia, Poland share six goals
"Their goalkeeper was incredible. We couldn't get a goal, but the performance was positive," Portugal coach Roberto Martinez told UEFA.com.
"Results are a consequence of performances and, tonight, we deserved to win."
Croatia are second in the group behind Portugal after playing out a thrilling 3-3 draw with Poland in Warsaw, in a match which featured five first-half goals.
Piotr Zielinski of Inter Milan, wearing the captain's armband as Robert Lewandowski started on the bench, gave Poland an early lead.
However, Borna Sosa equalised and further goals by Petar Sucic and Martin Baturina put Croatia 3-1 up after 26 minutes.
Nicola Zalewski pulled one back in first-half stoppage time and Sebastian Szymanski secured a draw for Poland with a terrific strike midway through the second half.
Croatia ended the game with 10 men after goalkeeper Dominik Livakovic was shown a red card for catching Lewandowski outside the area.
In the third-tier League C, Northern Ireland went top of Group 3 with a 5-0 win over Bulgaria in Belfast.
Isaac Price of Standard Liege in Belgium was the hero with a hat-trick, as the 21-year-old became the country's youngest ever scorer of three goals in a game. Northern Ireland's last hat-trick scorer was David Healy in 2007.
An own goal by goalkeeper Dimitar Mitov and a late Josh Magennis effort completed the scoring, while captain Kiril Despodov missed a penalty for Bulgaria.
Listen to the latest songs, only on JioSaavn.com
Belarus and Luxembourg drew 1-1 in the same group, while there were wins for Romania and Kosovo against Lithuania and Cyprus respectively in Group C2.
(This story has not been edited by staff and is auto-generated from a syndicated feed.)