周襄王十九年(前633年),楚成王准备围攻宋国,派前令尹子文在睽地演习练兵,派令尹子玉在蔫地作战前演习。子文一早就完事,没有惩罚一个人;子玉整整一天才结束,鞭打七人,箭穿三个人的耳朵。子文设宴,元老们都表示祝贺,年轻大夫为贾却不祝贺,认为子文把楚国政权让给子玉,而子玉刚愎自用,内不能治理百姓,对外率领兵车超过三百乘,恐怕就要吃败仗。子玉如果失败,那是由于子文的推荐,等到子玉胜利归来再祝贺,也不算迟。不久,楚成王便会同陈侯、蔡侯、郑伯,许男围宋。
宋国的公孙固急忙到晋国报急求救。晋国名将先轸认为,报答宋襄公在晋君流亡时的施舍,救援宋国被围之难,成就晋国的霸业,都在这一次了。晋文公之舅狐偃认为,楚国刚刚得到曹国,又新近同卫国结成婚姻,如果攻打曹、卫,楚国一定会救援,那么宋国和齐国就可以免遭楚军的进攻了。于是,晋国就在被庐举行大规模的阅兵式,组建上、中、下三军,晋文公委派却谷统率中军,却溱辅佐他;委派狐偃率领上军,狐偃把上军之帅让给狐毛,自己做副帅;又派栾枝率领下军,先轸辅佐他。荀林父为晋文公驾御戎车,魏犨为车右。
周襄王二十年(前632年)春,晋文公打算攻伐曹国,向卫国借路,卫国不答应。晋军回师,从南河渡过黄河,袭击曹国,攻打卫国。正月,晋军占领了卫国的五鹿;二月,晋中军元帅卻縠去世,先轸继任中军之帅,胥臣补先轸的空缺辅佐下军。晋侯与齐侯在卫国的敛盂结盟,卫成公向晋请求订立和约,晋国不答应。于是卫成公想投靠楚国,卫国的贵族不同意,就赶走他们的国君,以此来向晋国讨好。卫成公只好离开国都居住在襄牛。鲁国大夫公子买率鲁军助卫防守,楚军救援卫国,不能取胜。鲁僖公害怕晋国伐鲁,就杀了公子买来讨好晋国,对楚国人却说,公子买没有完成戍守的任务,所以杀了他。
晋军在打败卫国后,又包围了曹国国都,攻打城门,战死许多人。曹国的士卒把晋军的尸体陈列在城墙上,以此打击晋军士气。晋侯很为此事担忧,士卒们献策说:让军队在曹人的墓地扎营,示意掘他们祖先的坟。文公采纳了士卒们的意见,曹人果然非常恐惧,就把晋军的尸体用棺材装好送出城来,晋军乘机发起进攻,攻破曹都,俘获曹共公。晋文公当年流亡在曹,曹共公无礼地观看他洗澡,文公一直耿耿于怀,现在俘获了他,于是文公列举曹共公罪状,责备他不用贤臣僖负羁,却大封亲戚故旧,使曹这样一个小小的国家,大夫就多达三百余人。为了报答僖负羁当年赠飧置璧的恩惠,文公下令不许晋军进入僖负羁的住宅,同时赦免了他的族人。当年跟他流亡的魏犨、颠颉很生气,认为文公不考虑有功之臣,却去报答那些小恩小惠。于是两人带兵就放火烧了僖负羁的住宅,魏犫放火时伤了前胸。文公很恼怒,打算杀死他,但又爱惜他的勇武,就派人去察看伤情,如果伤势严重,就准备杀他。魏犨伤得不重,文公于是饶恕了他,杀颠颉在军中示众,又命舟之侨为兵车右卫。
晋军袭击卫国进攻曹国,原是为解宋国之围。但楚国见此二国已失,并不前来相救,反而率陈国、蔡国的军队加紧围攻宋国。于是宋国派大夫门尹般向晋君告急求救。晋文公十分为难,舍弃不管,宋国就会与晋绝交;请楚国退兵,楚国肯定不会答应;如果与楚国作战,齐国和秦国不会支持。进退两难之际,中军元帅先轸献上一计:让宋国用财物去贿赂齐、秦两国,请他们出面求楚国退兵,晋国扣留曹共公,然后分曹国、卫国的土地给宋国。楚国同曹、卫两国友好,其国土被分,必定不会答应齐、秦的调解,而齐、秦二国接受了宋国的贿赂、又恼恨楚国不给面子,就必然出兵伐楚。晋文公同意了先轸的离间计。果然,齐、秦与晋联合了起来。
楚成王见形势不利,退回申地(今河南南阳)驻扎,防备秦国的袭击,又命令戍守齐国谷邑的申公叔时和围攻宋国的子玉率部撤退,避免与晋军交战。他认为晋文公在外流亡了十九年,险阻艰难,全都经历了:民情真假,他都知道了,上天使他享有高年,同时除掉他的祸患。天所予,不可败。但是子玉却骄傲自负,坚持要与晋军作战,他说:虽不敢保证一定能建立什么了不起的战功,但希望用胜利堵住奸邪小人(指楚大夫为贾)进谗言的嘴。于是,子玉派大夫宛春到晋军中谈判,条件是恢复卫侯的君位,同时退还曹国的土地,楚军解除对宋国的包围。狐偃认为子玉太无礼,晋君(文公)只得到释宋之围一项好处,而楚臣(子玉)却得到恢复曹、卫两项好处。不能失掉这个战机。先轸不同意,他认为楚国一句话就使曹、卫复国、宋解围,三个国家都安全,晋国如果不同意,这三国就均被灭亡,这就是晋国无礼。不如暗中答应恢复曹、卫两国,使他们叛离楚国;再用扣留楚军使者宛春的办法来激怒子玉,等打完仗再考虑曹、卫的问题。晋文公很高兴,就采纳了先轸的意见。曹、卫两国果然派人到子玉营中同楚断交。子玉十分气愤,立即率军北上与晋军作战。文公见楚军逼近,下令退避三舍(古时一日行军30里为一舍)。将士们对后退很不理解,认为晋君躲避楚臣是极大的耻辱,何况楚军攻宋不下,在外转战多时,已经疲惫不振。狐偃向他们解释这样做是为了报答文公当年逃亡时楚君给予的恩惠,兑现文公当年所许的“两国若交兵,退避三舍相报”的诺言。于是晋文公、宋成公、齐国大夫国归父、崔夭、秦穆公之子小子愁率军退后九十里,在卫国的城濮(今山东濮县南)驻扎下来。楚军随即逼了上来,在城濮附近的险要地带扎营。
晋文公既害怕别人议论自己忘恩,又担心士卒不愿尽力作战,所以在与楚交战的问题上犹豫不决。三军将领都劝他下决心打。狐偃认为,这一仗若打胜,就可以得到诸侯国的拥戴,取得霸主的地位;即便打不胜,晋国外有黄河之阻,内有太行之险,没什么可担忧的。栾枝也说,汉水北面的姬姓国都被楚国吞并,思念小恩小惠而忘记大耻大辱,于国不利,应当与楚国交战!文公这才坚定了决心。
子玉误把晋军礼让性的后撤误认为是害怕楚军,于是刚扎下营盘便派大夫斗勃向晋文公挑战道:“请和您的部下游戏一番,您可以扶着车前横木观赏,我也陪您来开开眼界。”文公让栾枝回答说:我们国君领教命令了。楚王的恩惠不敢忘记。既然得不到谅解,那就烦请大夫告诉你们的将帅:准备好你们的战车,敬奉你们的君命,明天早晨战场上相见!
晋楚城濮大战前,晋军方面,有战车700乘,兵员37000人,另有齐、秦、宋的支援。中军元帅是先轸,却溱为副;上军主将是狐毛,狐偃为副;下军主将是栾枝,胥臣为副。楚军方面,子玉为中军主帅,指挥警卫王室的西广、东宫及若教六卒,共有战车180乘;子上为右军主将,指挥陈、蔡两国的军队;子西为左军主将,指挥申、息两地的地方部队。晋军的上军对楚军的左军,下军对右军,临战,子玉夸下海口说:“今天晋军必定覆没!”
战斗开始,晋下军副将胥臣命令士卒把驾车的马蒙上虎皮,首先向楚右军发起攻击,陈、蔡的军队跟从楚军本是不得已,遭到这一突然进攻,立即溃不成军,蔡国公子印也被杀死,晋上军主将狐毛另设前军两队,出击楚军的逃兵,楚军右翼被彻底打垮;晋下军主将栾枝让士卒砍伐木柴拖在车后,扬起尘土,伪装败逃,楚中军立即发起追击,左军主将子西求胜心切,以为晋军主力溃逃,带部率先追赶,造成楚军侧翼空虚。晋见楚中计,元帅先轸率领中军精锐拦腰截击,狐毛、狐偃反转回头杀来,前后夹击,楚国的左军也被打垮。子玉见左右两军全垮,急忙收兵,这才不至于全军覆灭。
城濮之战以晋胜楚败而告结束。晋军在楚营内歇兵三天,班师而归。向周天子献上俘获的战车100辆和俘虏的步兵1000人。周天子设宴款待晋文公,命令大臣尹氏、王子虎和内史叔兴父用策书颁命晋文公为诸侯之长,并赏赐了文公许多财物。
楚成王本不愿与晋交战,听说子玉大败而回,就派人对子玉说:“你失败回来,有何面目见申、息两地的父老呢?”逼子玉自杀谢罪。但在打发走使者后,成王又后悔起来,忙派人收回成命,这时子玉在连谷(今河南西华县南一带)已自杀了。
城濮之战使晋国国威大振,以前与楚国结盟的国家纷纷投靠晋国。文公在践土(今河南原阳县西南)建造王宫,与诸侯会盟,占据了霸主的地位。而楚国北上的战略在这一战中受到沉重的打击,此后一段时间只好转向南方经营。