It was South Africa's first series win under new captain Hashim Amla and only their second series victory in Sri Lanka in 21 years, and sees them overtake Australia as the world's number one team.
Set 369 for victory South Africa, who started the day at 38-1, finished on 159 for eight with Vernon Philander holding firm with an unbeaten 27.
That was despite a marathon bowling effort from spinners Rangana Herath and Dilruwan Perera who sent down 89 overs between them, including 54 maidens, to claim all eight South African wickets.
Sri Lanka had to fight not only against the strong defence put up by South Africa but also the weather that interrupted play three times during the day, including in the final session with South Africa on 118-6.
However the sun came out strongly and Sri Lanka grabbed the wicket of JP Duminy in the first over of the final hour when Perera trapped him lbw for three.
Herath picked up his 18th five wicket bag when he had Dale Steyn caught behind by Niroshan Dickwella for six.
There was no further joy for the hosts, however, as Philander and Imran Tahir played out the remaining 27 minutes.
Amla had led South Africa's resistance when he scored a fighting 25 off 159 balls before falling to Perera in the fourth over after tea when he edged an attempted drive to Mahela Jayawardene.
Herath also found the outside edge of Faf du Plessis' bat on 10 to give Jayawardene his 200th test catch.
Herath had also dismissed Quinton de Kock for 37 and AB de Villiers for 12 in the afternoon session, but the rain delays, which saw only 30 minutes play in the morning session, hindered Sri Lanka's push to level the series.
South Africa had lost the number one ranking to Australia in May.