Aleksandr Kerzhakov's 86th-minute header was enough for the visitors, who laboured to take advantage of Porto playing much of the game a man down.
The hosts found themselves reduced to 10 after six minutes of the Group G clash as Hector Herrera saw yellow twice in the space of a minute.
However, Zenit failed to make the extra man count for much of the game and, aside from a pair of Hulk chances in the second half, rarely threatened home goalkeeper Helton.
Porto more than held their own after the early setback, and twice hit the woodwork in a tie they arguably deserved to win. But the deadlock was finally broken four minutes from time as Kerzhakov sealed the three points for Luciano Spalletti's men.
The victory lifts Zenit to second in the pool, leapfrogging Porto in the process.
Paulo Fonseca made wholesale changes from the Porto team that triumphed over Segunda Liga basement club Trofense in the Taca de Portugal third round on Saturday.
Zenit, meanwhile, brought Cristian Ansaldi, Viktor Faizulin and Oleg Shatov in for Domenico Criscito, Konstantin Zyryanov and the suspended Axel Witsel after their 2-0 Russian top-flight win against CSKA Moscow last Friday.
The game got off to a nightmare start for the hosts at a far-from-full Estadio do Dragao, with Herrera sent off after receiving two yellow cards within six minutes.
His first for a reckless challenge on former Porto striker Hulk was immediately followed by a foolish second for encroachment on the resulting free-kick, meaning the Mexican marked his full Champions League debut with the fastest dismissal for a double booking in the competition’s history.
Despite being a man down, it was Porto who had the better of the chances early on, with Lucho Gonzalez rattling the angle between post and crossbar from 20 yards after 20 minutes and a stretching Lica unable to take advantage of the rebound.
Zenit struggled to settle and were reduced to speculative efforts from distance, with their best chance of the half being a curling Roman Shirokov effort from the edge of the area that drifted just beyond the far post.
With the match crying out for an injection of energy after the break, Porto substitute Silvestre Varela provided something of a spark as he cut in from the right, skipping past two defenders in the process, before skewing his strike wide of the left post.
Moments later at the other end, Hulk robbed Nicolas Otamendi of possession just outside the penalty area, but was denied by the outstretched foot of Helton after finding himself through one-on-one with the goalkeeper.
And Helton had to be alert again in the 65th minute, as Hulk powered a thunderbolt of a free-kick in his direction from 25 yards. It was too hot to handle, but the Brazilian punched clear to deny his former team-mate.
Varela, who looked lively after replacing Lica, went close for the hosts once again with just over 10 minutes left to play. He turned on the edge of the area and fired an effort against the Zenit crossbar, with goalkeeper Yuri Lodygin beaten.
However, it was Zenit who finally opened the scoring as Kerzhakov rose highest to head home from an excellent cross from Hulk on the right wing.
Get the best features, fun and footballing frolics straight to your inbox every week.
Thank you for signing up to Four Four Two. You will receive a verification email shortly.
There was a problem. Please refresh the page and try again.